diff --git a/src/GeneralTools/DataverseClient/Client/Builder/AbstractClientRequestBuilder.cs b/src/GeneralTools/DataverseClient/Client/Builder/AbstractClientRequestBuilder.cs
index 082c6be..427c52c 100644
--- a/src/GeneralTools/DataverseClient/Client/Builder/AbstractClientRequestBuilder.cs
+++ b/src/GeneralTools/DataverseClient/Client/Builder/AbstractClientRequestBuilder.cs
@@ -67,7 +67,14 @@ public T WithCorrelationId(Guid correlationId)
///
public T WithHeader(string key, string value)
{
- _headers.Add(key, value);
+ if ( _headers.ContainsKey(key))
+ {
+ _headers[key] = value;
+ }
+ else
+ {
+ _headers.Add(key, value);
+ }
return (T)this;
}
@@ -79,7 +86,16 @@ public T WithHeader(string key, string value)
public T WithHeaders(IDictionary headers)
{
foreach (var itm in headers)
- _headers.Add(itm.Key, itm.Value);
+ {
+ if( _headers.ContainsKey(itm.Key))
+ {
+ _headers[itm.Key] = itm.Value;
+ }
+ else
+ {
+ _headers.Add(itm.Key, itm.Value);
+ }
+ }
return (T)this;
}
diff --git a/src/GeneralTools/DataverseClient/Client/ConnectionService.cs b/src/GeneralTools/DataverseClient/Client/ConnectionService.cs
index 38c2edd..1151ba3 100644
--- a/src/GeneralTools/DataverseClient/Client/ConnectionService.cs
+++ b/src/GeneralTools/DataverseClient/Client/ConnectionService.cs
@@ -394,7 +394,7 @@ internal System.Net.NetworkCredential DataverseServiceAccessCredential
///
/// Type of protocol to use
///
- internal string InternetProtocalToUse { get { return _InternetProtocalToUse; } set { _InternetProtocalToUse = value; } }
+ internal string InternetProtocolToUse { get { return _InternetProtocalToUse; } set { _InternetProtocalToUse = value; } }
///
/// returns the connected organization detail object.
@@ -2337,6 +2337,8 @@ internal async Task Command_WebExecuteAsync(string queryStr
string requestIdLogSegement = logEntry.GetFormatedRequestSessionIdString(requestTrackingId, SessionTrackingId);
do
{
+ retry = false; // Set intial state.
+
// Add authorization header. - Here to catch the situation where a token expires during retry.
if (!customHeaders.ContainsKey(Utilities.RequestHeaders.AUTHORIZATION_HEADER))
customHeaders.Add(Utilities.RequestHeaders.AUTHORIZATION_HEADER, new List() { string.Format("Bearer {0}", await RefreshClientTokenAsync().ConfigureAwait(false)) });
diff --git a/src/GeneralTools/DataverseClient/Client/Connector/OnPremises/ServiceInformation.cs b/src/GeneralTools/DataverseClient/Client/Connector/OnPremises/ServiceInformation.cs
index c53c3f4..16c1f37 100644
--- a/src/GeneralTools/DataverseClient/Client/Connector/OnPremises/ServiceInformation.cs
+++ b/src/GeneralTools/DataverseClient/Client/Connector/OnPremises/ServiceInformation.cs
@@ -4,11 +4,11 @@
using System.IdentityModel.Tokens;
using System.Linq;
using System.Security;
-using System.Security.Permissions;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.Text;
+
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Common;
@@ -52,6 +52,7 @@ internal ServiceConfiguration(Uri serviceUri, bool checkForSecondary)
ClientExceptionHelper.ThrowIfNull(ServiceEndpointMetadata, "ServiceEndpointMetadata");
+#if NETFRAMEWORK
if (ServiceEndpointMetadata.ServiceEndpoints.Count == 0)
{
StringBuilder errorBuilder = new StringBuilder();
@@ -65,6 +66,7 @@ internal ServiceConfiguration(Uri serviceUri, bool checkForSecondary)
throw new InvalidOperationException(ClientExceptionHelper.FormatMessage(0, "The provided uri did not return any Service Endpoints!\n{0}", errorBuilder.ToString()));
}
+#endif
ServiceEndpoints = ServiceEndpointMetadata.ServiceEndpoints;
@@ -665,20 +667,20 @@ private SecurityTokenResponse AuthenticateInternal(AuthenticationCredentials aut
{
return Issue(authenticationCredentials);
}
- catch (SecurityTokenValidationException)
- {
- retry = false;
-
- // Fall back to windows integrated.
- if (authenticationCredentials.IssuerEndpoints.ContainsKey(TokenServiceCredentialType.Windows.ToString()))
- {
- authenticationCredentials.EndpointType = TokenServiceCredentialType.Windows;
- retry = ++retryCount < 2;
- }
-
- // We don't care, we just want to return null. The reason why we are are catching this one is because in pure Kerberos mode, this
- // will throw a very bad exception that will crash VS.
- }
+ //catch (SecurityTokenValidationException) // Removed due to a type conflict with DV Server
+ //{
+ // retry = false;
+
+ // // Fall back to windows integrated.
+ // if (authenticationCredentials.IssuerEndpoints.ContainsKey(TokenServiceCredentialType.Windows.ToString()))
+ // {
+ // authenticationCredentials.EndpointType = TokenServiceCredentialType.Windows;
+ // retry = ++retryCount < 2;
+ // }
+
+ // // We don't care, we just want to return null. The reason why we are are catching this one is because in pure Kerberos mode, this
+ // // will throw a very bad exception that will crash VS.
+ //}
catch (SecurityNegotiationException)
{
// This is the exception with Integrated Windows Auth.
diff --git a/src/GeneralTools/DataverseClient/Client/Connector/OnPremises/ServiceMetadataUtility.cs b/src/GeneralTools/DataverseClient/Client/Connector/OnPremises/ServiceMetadataUtility.cs
index a7a73a9..1519eec 100644
--- a/src/GeneralTools/DataverseClient/Client/Connector/OnPremises/ServiceMetadataUtility.cs
+++ b/src/GeneralTools/DataverseClient/Client/Connector/OnPremises/ServiceMetadataUtility.cs
@@ -16,7 +16,6 @@
using System.Text;
using System.Xml;
using Microsoft.PowerPlatform.Dataverse.Client.Utils;
-//using Microsoft.Crm.Protocols.WSTrust.Bindings;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Common;
@@ -262,13 +261,13 @@ public static IssuerEndpoint GetIssuer(Binding binding)
}
return null;
- }
+ }
+#if NETFRAMEWORK
private static KerberosSecurityTokenParameters GetKerberosTokenParameters(SecurityBindingElement securityElement)
{
if (securityElement != null)
{
-#if NETFRAMEWORK
if (securityElement.EndpointSupportingTokenParameters != null)
{
if (securityElement.EndpointSupportingTokenParameters.Endorsing != null)
@@ -279,15 +278,12 @@ private static KerberosSecurityTokenParameters GetKerberosTokenParameters(Securi
}
}
}
-#else
- throw new PlatformNotSupportedException("Xrm.Sdk WSTrust");
-#endif
}
-
return null;
}
+#endif
- private static IssuedSecurityTokenParameters GetIssuedTokenParameters(SecurityBindingElement securityElement)
+ private static IssuedSecurityTokenParameters GetIssuedTokenParameters(SecurityBindingElement securityElement)
{
if (securityElement != null)
{
@@ -351,15 +347,15 @@ public static CustomBinding SetIssuer(Binding binding, IssuerEndpoint issuerEndp
}
return new CustomBinding(elements);
- }
+ }
+#if NETFRAMEWORK
private static void ParseEndpoints(ServiceEndpointDictionary serviceEndpoints, ServiceEndpointCollection serviceEndpointCollection)
{
serviceEndpoints.Clear();
if (serviceEndpointCollection != null)
{
-#if NETFRAMEWORK
foreach (var endpoint in serviceEndpointCollection)
{
if (IsEndpointSupported(endpoint))
@@ -367,13 +363,11 @@ private static void ParseEndpoints(ServiceEndpointDictionary serviceEndpoints, S
serviceEndpoints.Add(endpoint.Name, endpoint);
}
}
-#else
- throw new PlatformNotSupportedException("Xrm.Sdk WSDL");
-#endif
}
}
+#endif
- private static bool IsEndpointSupported(ServiceEndpoint endpoint)
+ private static bool IsEndpointSupported(ServiceEndpoint endpoint)
{
if (endpoint != null)
{
@@ -389,6 +383,7 @@ private static bool IsEndpointSupported(ServiceEndpoint endpoint)
internal static ServiceEndpointMetadata RetrieveServiceEndpointMetadata(Type contractType, Uri serviceUri, bool checkForSecondary)
{
+#if NETFRAMEWORK // WebInfra; MetadataSet and CreateMetadataClient are NETFRAMEWORK-ONLY
ServiceEndpointMetadata serviceEndpointMetadata = new ServiceEndpointMetadata();
serviceEndpointMetadata.ServiceUrls = ServiceConfiguration.CalculateEndpoints(serviceUri);
@@ -398,25 +393,17 @@ internal static ServiceEndpointMetadata RetrieveServiceEndpointMetadata(Type con
serviceEndpointMetadata.ServiceUrls.AlternateEndpoint = null;
}
-#if !NETFRAMEWORK
- // TODO: Waiting on work for updated WCF endpoints collection to be completed. // hard throw here to prevent any futher progress.
- throw new PlatformNotSupportedException("Xrm.Sdk WSDL");
-#endif
-
// Get version of current assembly which is the version of the SDK
-#pragma warning disable CS0162 // Unreachable code detected
Version sdkVersion = GetSDKVersionNumberFromAssembly();
-#pragma warning restore CS0162 // Unreachable code detected
var wsdlUri = new Uri(string.Format(CultureInfo.InvariantCulture, "{0}{1}&sdkversion={2}", serviceUri.AbsoluteUri, "?wsdl", sdkVersion.ToString(2)));
var mcli = CreateMetadataClient(wsdlUri.Scheme);
if (mcli != null)
{
-#if NETFRAMEWORK
try
{
serviceEndpointMetadata.ServiceMetadata = mcli.GetMetadata(wsdlUri, MetadataExchangeClientMode.HttpGet);
- }
+ }
catch (InvalidOperationException ioexp)
{
bool rethrow = true;
@@ -447,29 +434,14 @@ internal static ServiceEndpointMetadata RetrieveServiceEndpointMetadata(Type con
throw;
}
}
-#else
- throw new PlatformNotSupportedException("Xrm.Sdk WSDL");
-#endif
}
- else
- {
-#if !NETFRAMEWORK
- if (serviceEndpointMetadata.ServiceMetadata == null)
- serviceEndpointMetadata.ServiceMetadata = new MetadataSet();
- var MetadataBody = GetMexDocument(wsdlUri);
-#else
- throw new PlatformNotSupportedException("Xrm.Sdk WSDL");
-#endif
- }
-
- ClientExceptionHelper.ThrowIfNull(serviceEndpointMetadata.ServiceMetadata, "STS Metadata");
+ ClientExceptionHelper.ThrowIfNull(serviceEndpointMetadata.ServiceMetadata, "STS Metadata");
var contracts = CreateContractCollection(contractType);
if (contracts != null)
{
-#if NETFRAMEWORK
// The following code inserts a custom WsdlImporter without removing the other
// importers already in the collection.
var importer = new WsdlImporter(serviceEndpointMetadata.ServiceMetadata);
@@ -497,34 +469,15 @@ internal static ServiceEndpointMetadata RetrieveServiceEndpointMetadata(Type con
}
ParseEndpoints(serviceEndpointMetadata.ServiceEndpoints, endpoints);
-#else
-
- // Dataverse requires Message Transport security which is not supported in .net core for ActiveDirectory.
-
-
- //AuthenticationPolicy authenticationPolicy = new AuthenticationPolicy();
- //authenticationPolicy.PolicyElements.Add("AuthenticationType", "ActiveDirectory"); // Need to read these from metdata in the future if WCF does not provide support/.
- //TextMessageEncodingBindingElement text01 = new TextMessageEncodingBindingElement();
- //HttpsTransportBindingElement http1 = new HttpsTransportBindingElement();
- //http1.ExtendedProtectionPolicy = new System.Security.Authentication.ExtendedProtection.ExtendedProtectionPolicy(System.Security.Authentication.ExtendedProtection.PolicyEnforcement.WhenSupported, System.Security.Authentication.ExtendedProtection.ProtectionScenario.TransportSelected, null);
- //CustomBinding bind = new CustomBinding(authenticationPolicy, new TextMessageEncodingBindingElement(), http1);
- //bind.Name = "CustomBinding_IOrganizationService";
- //bind.Namespace = "http://schemas.microsoft.com/xrm/2011/Contracts/Services";
- //serviceEndpointMetadata.ServiceEndpoints.Add(
- // "CustomBinding_IOrganizationService",
- // new ServiceEndpoint(contracts[0],
- // bind,
- // new EndpointAddress(serviceEndpointMetadata.ServiceUrls.PrimaryEndpoint)));
-
-
- throw new PlatformNotSupportedException("Xrm.Sdk WSDL");
-#endif
}
return serviceEndpointMetadata;
+#else
+ throw new NotImplementedException("ServiceModel metadata support is limited for this target framework");
+#endif
}
- private static Version GetSDKVersionNumberFromAssembly()
+ private static Version GetSDKVersionNumberFromAssembly()
{
string fileVersion = OrganizationServiceProxy.GetXrmSdkAssemblyFileVersion();
@@ -536,8 +489,9 @@ private static Version GetSDKVersionNumberFromAssembly()
}
return parsedVersion;
- }
+ }
+#if NETFRAMEWORK
///
/// Returns a list of policy import extensions in the importer parameter and adds a SecurityBindingElementImporter if not already present in the list.
///
@@ -546,7 +500,7 @@ private static Version GetSDKVersionNumberFromAssembly()
private static List AddSecurityBindingToPolicyImporter(WsdlImporter importer)
{
List newExts = new List();
-#if NETFRAMEWORK
+
KeyedByTypeCollection policyExtensions = importer.PolicyImportExtensions;
SecurityBindingElementImporter securityBindingElementImporter = policyExtensions.Find();
@@ -564,18 +518,14 @@ private static List AddSecurityBindingToPolicyImporter(W
newExts.AddRange(policyExtensions);
return newExts;
-#else
-
- newExts.Add(new AuthenticationPolicyImporter(new SecurityBindingElementImporter()));
- return newExts;
- //throw new PlatformNotSupportedException("Xrm.Sdk WSDL");
-#endif
}
+#endif
- [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to catch any exception here and fail.")]
+#if NETFRAMEWORK
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to catch any exception here and fail.")]
private static bool TryRetrieveMetadata(MetadataExchangeClient mcli, Uri serviceEndpoint, ServiceEndpointMetadata serviceEndpointMetadata)
{
-#if NETFRAMEWORK
+
bool rethrow = true;
try
{
@@ -589,12 +539,10 @@ private static bool TryRetrieveMetadata(MetadataExchangeClient mcli, Uri service
}
return rethrow;
-#else
- throw new PlatformNotSupportedException("Xrm.Sdk WSDL");
-#endif
}
+#endif
- private static XmlQualifiedName GetPortTypeQName(ContractDescription contract)
+ private static XmlQualifiedName GetPortTypeQName(ContractDescription contract)
{
return new XmlQualifiedName(contract.Name, contract.Namespace);
}
@@ -602,11 +550,11 @@ private static XmlQualifiedName GetPortTypeQName(ContractDescription contract)
private static Collection CreateContractCollection(Type contract)
{
return new Collection { ContractDescription.GetContract(contract) };
- }
+ }
+#if NETFRAMEWORK
private static MetadataExchangeClient CreateMetadataClient(string scheme)
{
-#if NETFRAMEWORK
WSHttpBinding mexBinding = null;
if (string.Compare(scheme, "https", StringComparison.OrdinalIgnoreCase) == 0)
@@ -628,13 +576,10 @@ private static MetadataExchangeClient CreateMetadataClient(string scheme)
mcli.MaximumResolvedReferences = 100;
return mcli;
-#else
- return null;
- //throw new PlatformNotSupportedException("Xrm.Sdk WSDL");
-#endif
}
+#endif
- public static void ReplaceEndpointAddress(ServiceEndpoint endpoint, Uri adddress)
+ public static void ReplaceEndpointAddress(ServiceEndpoint endpoint, Uri adddress)
{
var addressBuilder = new EndpointAddressBuilder(endpoint.Address);
addressBuilder.Uri = adddress;
diff --git a/src/GeneralTools/DataverseClient/Client/DataverseTelemetryBehaviors.cs b/src/GeneralTools/DataverseClient/Client/DataverseTelemetryBehaviors.cs
index 7264d1a..22bb328 100644
--- a/src/GeneralTools/DataverseClient/Client/DataverseTelemetryBehaviors.cs
+++ b/src/GeneralTools/DataverseClient/Client/DataverseTelemetryBehaviors.cs
@@ -369,8 +369,8 @@ public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
using (OperationContextScope scope = new OperationContextScope((IContextChannel)channel))
{
- var ForceConsistencytHeader = new MessageHeader("Strong").GetUntypedHeader(Utilities.RequestHeaders.FORCE_CONSISTENCY, "http://schemas.microsoft.com/xrm/2011/Contracts");
- request.Headers.Add(ForceConsistencytHeader);
+ var ForceConsistencyHeader = new MessageHeader("Strong").GetUntypedHeader(Utilities.RequestHeaders.FORCE_CONSISTENCY, "http://schemas.microsoft.com/xrm/2011/Contracts");
+ request.Headers.Add(ForceConsistencyHeader);
}
}
return null;
diff --git a/src/GeneralTools/DataverseClient/Client/Microsoft.PowerPlatform.Dataverse.Client.csproj b/src/GeneralTools/DataverseClient/Client/Microsoft.PowerPlatform.Dataverse.Client.csproj
index eebfc46..1dbd599 100644
--- a/src/GeneralTools/DataverseClient/Client/Microsoft.PowerPlatform.Dataverse.Client.csproj
+++ b/src/GeneralTools/DataverseClient/Client/Microsoft.PowerPlatform.Dataverse.Client.csproj
@@ -34,22 +34,31 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/GeneralTools/DataverseClient/Client/ServiceClient.cs b/src/GeneralTools/DataverseClient/Client/ServiceClient.cs
index d241467..b0200ec 100644
--- a/src/GeneralTools/DataverseClient/Client/ServiceClient.cs
+++ b/src/GeneralTools/DataverseClient/Client/ServiceClient.cs
@@ -578,6 +578,12 @@ public bool ForceServerMetadataCacheConsistency
}
set
{
+ if (ConnectedOrgVersion == Version.Parse("9.0.0.0")) // Default setting found as this is a version number that is hard set during setup of connection. it is not possible to actually have an environment with this version number
+ {
+ //force update version
+ _logEntry.Log($"Requested current version from Dataverse, found: {OrganizationDetail.OrganizationVersion}");
+ }
+
if (_connectionSvc != null && Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(_connectionSvc?.OrganizationVersion, Utilities.FeatureVersionMinimums.ForceConsistencySupported))
_connectionSvc.ForceServerCacheConsistency = value;
else
@@ -1269,7 +1275,7 @@ internal void CreateServiceConnection(
_connectionSvc.RequestAdditionalHeadersAsync = GetCustomHeaders;
// Assign the log entry host to the ConnectionService engine
ConnectionService tempConnectService = null;
- _connectionSvc.InternetProtocalToUse = useSsl ? "https" : "http";
+ _connectionSvc.InternetProtocolToUse = useSsl ? "https" : "http";
if (!_connectionSvc.DoLogin(out tempConnectService))
{
_logEntry.Log("Unable to Login to Dataverse", TraceEventType.Error);
diff --git a/src/GeneralTools/DataverseClient/DataverseClient.sln b/src/GeneralTools/DataverseClient/DataverseClient.sln
index dc1e6e0..a427964 100644
--- a/src/GeneralTools/DataverseClient/DataverseClient.sln
+++ b/src/GeneralTools/DataverseClient/DataverseClient.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.29609.76
+# Visual Studio Version 17
+VisualStudioVersion = 17.10.35027.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerPlatform.Dataverse.Client", "Client\Microsoft.PowerPlatform.Dataverse.Client.csproj", "{7303AAC5-BCEF-4BDB-B7D8-303490D506C6}"
EndProject
@@ -11,12 +11,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataverseClient_Core_UnitTe
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerPlatform.Dataverse.Client.Dynamics", "Extensions\DynamicsExtension\Microsoft.PowerPlatform.Dataverse.Client.Dynamics.csproj", "{8CE32D7B-EA3D-4725-A270-9780D366EDB7}"
EndProject
-#Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Dynamics.Sdk.Messages.Shell", "Extensions\Microsoft.Dynamics.Sdk.Messages\Microsoft.Dynamics.Sdk.Messages.Shell.csproj", "{503645DD-7711-40ED-8811-F06391F294AB}"
-#EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiveTestsConsole", "UnitTests\LiveTestsConsole\LiveTestsConsole.csproj", "{5A1A4FFF-78F5-48A2-9AB0-3E507E938465}"
EndProject
-#Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerPlatform.Dataverse.ServiceClientConverter", "Extensions\Microsoft.PowerPlatform.Dataverse.ServiceClientConverter\Microsoft.PowerPlatform.Dataverse.ServiceClientConverter.csproj", "{752E5268-3D99-485D-A31E-FC40AE9C0867}"
-#EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzDevOps_ServiceConnection_Test", "UnitTests\AzDevOps_ServiceConnection_Test\AzDevOps_ServiceConnection_Test.csproj", "{581F17CF-C7DE-4147-8764-E6B4328C07E7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerPlatform.Dataverse.Client.AzAuth", "Extensions\Microsoft.PowerPlatform.Dataverse.Client.AzAuth\Microsoft.PowerPlatform.Dataverse.Client.AzAuth.csproj", "{6A891168-EACF-4AC1-B081-ABA2FFF0DA7D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerPlatform.Dataverse.Client.PowerShell", "PowerShell\Microsoft.PowerPlatform.Dataverse.Client.PowerShell\Microsoft.PowerPlatform.Dataverse.Client.PowerShell.csproj", "{849A9DED-F4C6-4CFE-BA25-696F6E100198}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -35,18 +37,22 @@ Global
{8CE32D7B-EA3D-4725-A270-9780D366EDB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CE32D7B-EA3D-4725-A270-9780D366EDB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CE32D7B-EA3D-4725-A270-9780D366EDB7}.Release|Any CPU.Build.0 = Release|Any CPU
- {503645DD-7711-40ED-8811-F06391F294AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {503645DD-7711-40ED-8811-F06391F294AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {503645DD-7711-40ED-8811-F06391F294AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {503645DD-7711-40ED-8811-F06391F294AB}.Release|Any CPU.Build.0 = Release|Any CPU
{5A1A4FFF-78F5-48A2-9AB0-3E507E938465}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A1A4FFF-78F5-48A2-9AB0-3E507E938465}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A1A4FFF-78F5-48A2-9AB0-3E507E938465}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A1A4FFF-78F5-48A2-9AB0-3E507E938465}.Release|Any CPU.Build.0 = Release|Any CPU
- {752E5268-3D99-485D-A31E-FC40AE9C0867}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {752E5268-3D99-485D-A31E-FC40AE9C0867}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {752E5268-3D99-485D-A31E-FC40AE9C0867}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {752E5268-3D99-485D-A31E-FC40AE9C0867}.Release|Any CPU.Build.0 = Release|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6A891168-EACF-4AC1-B081-ABA2FFF0DA7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6A891168-EACF-4AC1-B081-ABA2FFF0DA7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6A891168-EACF-4AC1-B081-ABA2FFF0DA7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6A891168-EACF-4AC1-B081-ABA2FFF0DA7D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {849A9DED-F4C6-4CFE-BA25-696F6E100198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {849A9DED-F4C6-4CFE-BA25-696F6E100198}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {849A9DED-F4C6-4CFE-BA25-696F6E100198}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {849A9DED-F4C6-4CFE-BA25-696F6E100198}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -54,6 +60,7 @@ Global
GlobalSection(NestedProjects) = preSolution
{F85F6B93-D17D-4CAE-9B2F-D293BE73C700} = {CD32538C-E929-42F9-A936-5550427E2CD5}
{5A1A4FFF-78F5-48A2-9AB0-3E507E938465} = {CD32538C-E929-42F9-A936-5550427E2CD5}
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7} = {CD32538C-E929-42F9-A936-5550427E2CD5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EA6D26FC-C065-4849-959E-1EF7FC013AF2}
diff --git a/src/GeneralTools/DataverseClient/DataverseClientWithConnector.sln b/src/GeneralTools/DataverseClient/DataverseClientWithConnector.sln
index 0c60704..6b19603 100644
--- a/src/GeneralTools/DataverseClient/DataverseClientWithConnector.sln
+++ b/src/GeneralTools/DataverseClient/DataverseClientWithConnector.sln
@@ -23,7 +23,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerPlatform.Dat
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerPlatform.Dataverse.WebResourceUtility", "WebResourceUtility\Microsoft.PowerPlatform.Dataverse.WebResourceUtility.csproj", "{FDFD6B7F-A925-40EE-98DC-2E06C1D1E3B6}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerPlatform.Dataverse.Client.AzAuth", "Extensions\Microsoft.PowerPlatform.Dataverse.Client.AzAuth\Microsoft.PowerPlatform.Dataverse.Client.AzAuth.csproj", "{618D52B7-4CE7-402F-972B-E381C1C28299}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerPlatform.Dataverse.Client.AzAuth", "Extensions\Microsoft.PowerPlatform.Dataverse.Client.AzAuth\Microsoft.PowerPlatform.Dataverse.Client.AzAuth.csproj", "{618D52B7-4CE7-402F-972B-E381C1C28299}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzDevOps_ServiceConnection_Test", "UnitTests\AzDevOps_ServiceConnection_Test\AzDevOps_ServiceConnection_Test.csproj", "{581F17CF-C7DE-4147-8764-E6B4328C07E7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerPlatform.Dataverse.Client.PowerShell", "PowerShell\Microsoft.PowerPlatform.Dataverse.Client.PowerShell\Microsoft.PowerPlatform.Dataverse.Client.PowerShell.csproj", "{8FD12028-BBEF-448B-BD43-E352386149DD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -143,6 +147,30 @@ Global
{618D52B7-4CE7-402F-972B-E381C1C28299}.Release|Any CPU.Build.0 = Release|Any CPU
{618D52B7-4CE7-402F-972B-E381C1C28299}.Release|x64.ActiveCfg = Release|Any CPU
{618D52B7-4CE7-402F-972B-E381C1C28299}.Release|x64.Build.0 = Release|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.CRMINTERNAL|Any CPU.ActiveCfg = Debug|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.CRMINTERNAL|Any CPU.Build.0 = Debug|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.CRMINTERNAL|x64.ActiveCfg = Debug|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.CRMINTERNAL|x64.Build.0 = Debug|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.Debug|x64.Build.0 = Debug|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.Release|x64.ActiveCfg = Release|Any CPU
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7}.Release|x64.Build.0 = Release|Any CPU
+ {8FD12028-BBEF-448B-BD43-E352386149DD}.CRMINTERNAL|Any CPU.ActiveCfg = Debug|Any CPU
+ {8FD12028-BBEF-448B-BD43-E352386149DD}.CRMINTERNAL|Any CPU.Build.0 = Debug|Any CPU
+ {8FD12028-BBEF-448B-BD43-E352386149DD}.CRMINTERNAL|x64.ActiveCfg = Debug|Any CPU
+ {8FD12028-BBEF-448B-BD43-E352386149DD}.CRMINTERNAL|x64.Build.0 = Debug|Any CPU
+ {8FD12028-BBEF-448B-BD43-E352386149DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8FD12028-BBEF-448B-BD43-E352386149DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8FD12028-BBEF-448B-BD43-E352386149DD}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8FD12028-BBEF-448B-BD43-E352386149DD}.Debug|x64.Build.0 = Debug|Any CPU
+ {8FD12028-BBEF-448B-BD43-E352386149DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8FD12028-BBEF-448B-BD43-E352386149DD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8FD12028-BBEF-448B-BD43-E352386149DD}.Release|x64.ActiveCfg = Release|Any CPU
+ {8FD12028-BBEF-448B-BD43-E352386149DD}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -151,6 +179,7 @@ Global
{F85F6B93-D17D-4CAE-9B2F-D293BE73C700} = {CD32538C-E929-42F9-A936-5550427E2CD5}
{5A1A4FFF-78F5-48A2-9AB0-3E507E938465} = {CD32538C-E929-42F9-A936-5550427E2CD5}
{E24CC0FA-4686-448E-A9AC-7A2B58D07FF6} = {01915F6D-79FF-4118-9EAD-2470351C8035}
+ {581F17CF-C7DE-4147-8764-E6B4328C07E7} = {CD32538C-E929-42F9-A936-5550427E2CD5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EA6D26FC-C065-4849-959E-1EF7FC013AF2}
diff --git a/src/GeneralTools/DataverseClient/Extensions/Microsoft.PowerPlatform.Dataverse.Client.AzAuth/AzAuth.cs b/src/GeneralTools/DataverseClient/Extensions/Microsoft.PowerPlatform.Dataverse.Client.AzAuth/AzAuth.cs
index ff72d4b..72f3e17 100644
--- a/src/GeneralTools/DataverseClient/Extensions/Microsoft.PowerPlatform.Dataverse.Client.AzAuth/AzAuth.cs
+++ b/src/GeneralTools/DataverseClient/Extensions/Microsoft.PowerPlatform.Dataverse.Client.AzAuth/AzAuth.cs
@@ -108,8 +108,11 @@ public async Task GetAccessToken(string instanceUrl)
if (_cacheList.ContainsKey(instanceUri))
{
accessToken = _cacheList[instanceUri];
- if (accessToken.HasValue && accessToken.Value.ExpiresOn < DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(30)))
+ if (accessToken.HasValue && accessToken.Value.ExpiresOn < DateTimeOffset.UtcNow.Subtract(TimeSpan.FromSeconds(30)))
+ {
accessToken = null; // flush the access token if it is about to expire.
+ _cacheList.Remove(instanceUri);
+ }
}
if ( accessToken == null)
@@ -137,22 +140,25 @@ public async Task GetAccessToken(string instanceUrl)
return accessToken.Value.Token;
}
- private string[] ResolveScopesList(Uri instanceUrl , Uri resource = null)
+ ///
+ /// gets or creates the scope list for the current instance.
+ ///
+ ///
+ ///
+ ///
+ ///
+ private string[] ResolveScopesList(Uri instanceUrl, Uri resource = null)
{
_scopesList ??= new Dictionary>();
- if ( _scopesList.ContainsKey(instanceUrl))
- {
- return _scopesList[instanceUrl].ToArray();
- }
+
+ if (_scopesList.TryGetValue(instanceUrl, out List foundList))
+ return foundList.ToArray();
+
if (resource == null)
- {
throw new ArgumentNullException("Resource URI is required");
- }
- else
- {
- _scopesList.Add(instanceUrl, new List { $"{resource}.default" });
- return _scopesList[instanceUrl].ToArray();
- }
+
+ _scopesList.Add(instanceUrl, new List { $"{resource}.default" });
+ return _scopesList[instanceUrl].ToArray();
}
///
diff --git a/src/GeneralTools/DataverseClient/Extensions/Microsoft.PowerPlatform.Dataverse.Client.AzAuth/AzPipelineFederatedIdentityAuth.cs b/src/GeneralTools/DataverseClient/Extensions/Microsoft.PowerPlatform.Dataverse.Client.AzAuth/AzPipelineFederatedIdentityAuth.cs
new file mode 100644
index 0000000..69eaf0a
--- /dev/null
+++ b/src/GeneralTools/DataverseClient/Extensions/Microsoft.PowerPlatform.Dataverse.Client.AzAuth/AzPipelineFederatedIdentityAuth.cs
@@ -0,0 +1,189 @@
+using Azure.Core;
+using Azure.Identity;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using Microsoft.PowerPlatform.Dataverse.Client.Model;
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace Microsoft.PowerPlatform.Dataverse.Client
+{
+ ///
+ /// Auth class that will create a Workload Identity based authentication for Dataverse Service Client using the specified Azure DevOps Service Connection.
+ ///
+ public class AzPipelineFederatedIdentityAuth
+ {
+ private AzurePipelinesCredential _pipelineCredential;
+ private AzurePipelinesCredentialOptions _credentialOptions;
+ private readonly bool _autoResolveAuthorityAndTenant;
+ private Dictionary> _scopesList;
+ private Dictionary _cacheList;
+ private ILogger _logger;
+ private string _tenantId;
+ private string _clientId;
+ private string _serviceConnectionId;
+ private string _systemAccessTokenEnvVarName;
+
+ ///
+ /// Creates a new instance of the ServiceClient class using the AzDevOps Service Connection
+ ///
+ /// TenantId for the service connection
+ /// ClientId for the service connection
+ /// Service Connection Id of AzDevOps ServiceConnection configured for workload identity
+ /// Dataverse ServiceClient Connection Options
+ /// Dataverse ServiceClient Configuration Options. Default = null
+ /// Environment Variable that has the current AzDevOps System Access Token. Default=SYSTEM_ACCESSTOKEN
+ ///
+ ///
+ public static ServiceClient CreateServiceClient(
+ string tenantId,
+ string clientId,
+ string serviceConnectionId,
+ ConnectionOptions connectionOptions,
+ ConfigurationOptions configurationOptions = null,
+ string systemAccessTokenEnvVarName = "SYSTEM_ACCESSTOKEN")
+ {
+ if (connectionOptions == null)
+ {
+ throw new ArgumentException("ConnectionOptions are required");
+ }
+ if (connectionOptions.ServiceUri == null)
+ {
+ throw new ArgumentException("ConnectionOptions.ServiceUri is required");
+ }
+ connectionOptions.AuthenticationType = AuthenticationType.ExternalTokenManagement; // force the authentication type to be external token management.
+
+ AzPipelineFederatedIdentityAuth azAuth = new AzPipelineFederatedIdentityAuth( tenantId, clientId, serviceConnectionId, systemAccessTokenEnvVarName, true, connectionOptions.Logger);
+ connectionOptions.AccessTokenProviderFunctionAsync = azAuth.GetAccessToken;
+ return new ServiceClient(connectionOptions, false, configurationOptions);
+ }
+
+ ///
+ /// Creates an instance of the AzPipelineFederatedIdentityAuth class
+ ///
+ /// Should resolve Dataverse authority and resource from url.
+ /// Service Connection Id of AzDevOps ServiceConnection configured for workload identity
+ /// ClientId for the service connection
+ /// TenantId for the service connection
+ /// Environment Variable that has the current AzDevOps System Access Token. Default=SYSTEM_ACCESSTOKEN
+ /// ILogger instance
+ public AzPipelineFederatedIdentityAuth(string tenantId, string clientId, string serviceConnectionId, string SystemAccessTokenEnvVarName, bool autoResolveAuthorityAndTenant, ILogger logger = null)
+ {
+ _tenantId = tenantId;
+ _clientId = clientId;
+ _serviceConnectionId = serviceConnectionId;
+ _systemAccessTokenEnvVarName = SystemAccessTokenEnvVarName;
+ _autoResolveAuthorityAndTenant = autoResolveAuthorityAndTenant;
+ _logger = logger;
+ }
+
+ ///
+ /// Returns the current access token for the connected ServiceClient instance
+ ///
+ ///
+ ///
+ public async Task GetAccessToken(string instanceUrl)
+ {
+ if (!Uri.IsWellFormedUriString(instanceUrl, UriKind.RelativeOrAbsolute))
+ {
+ throw new ArgumentException("Invalid instance URL");
+ }
+ AccessToken? accessToken = null;
+ Uri instanceUri = new Uri(instanceUrl);
+ if (_pipelineCredential == null)
+ {
+ Uri resourceUri = await InitializeCredentials(instanceUri).ConfigureAwait(false);
+ ResolveScopesList(instanceUri, resourceUri);
+ }
+
+ // Get or create existing token.
+ _cacheList ??= new Dictionary();
+ if (_cacheList.ContainsKey(instanceUri))
+ {
+ accessToken = _cacheList[instanceUri];
+ if (accessToken.HasValue && accessToken.Value.ExpiresOn < DateTimeOffset.UtcNow.Subtract(TimeSpan.FromSeconds(30)))
+ {
+ accessToken = null; // flush the access token if it is about to expire.
+ _cacheList.Remove(instanceUri);
+ }
+ }
+
+ if (accessToken == null)
+ {
+ Stopwatch sw = Stopwatch.StartNew();
+ _logger.LogDebug("Getting new access token for {0}", instanceUri);
+ accessToken = await _pipelineCredential.GetTokenAsync(new TokenRequestContext(ResolveScopesList(instanceUri)), System.Threading.CancellationToken.None).ConfigureAwait(false);
+ _logger.LogDebug("Access token retrieved in {0}ms", sw.ElapsedMilliseconds);
+ sw.Stop();
+ if (_cacheList.ContainsKey(instanceUri))
+ {
+ _cacheList[instanceUri] = accessToken;
+ }
+ else
+ {
+ _cacheList.Add(instanceUri, accessToken);
+ }
+ }
+
+ if (accessToken == null)
+ {
+ throw new Exception("Failed to retrieve access token");
+ }
+
+ return accessToken.Value.Token;
+ }
+
+ private string[] ResolveScopesList(Uri instanceUrl, Uri resource = null)
+ {
+ _scopesList ??= new Dictionary>();
+
+ if (_scopesList.TryGetValue(instanceUrl, out List foundList))
+ return foundList.ToArray();
+
+ if (resource == null)
+ throw new ArgumentNullException("Resource URI is required");
+
+ _scopesList.Add(instanceUrl, new List { $"{resource}.default" });
+ return _scopesList[instanceUrl].ToArray();
+ }
+
+ ///
+ /// Initialize the credentials for the current instance
+ ///
+ ///
+ ///
+ private async Task InitializeCredentials(Uri instanceUrl)
+ {
+ _logger.LogDebug("Initializing credentials for {0}", instanceUrl);
+ Stopwatch sw = Stopwatch.StartNew();
+
+ Uri resourceUri = null;
+ _credentialOptions ??= new AzurePipelinesCredentialOptions();
+
+ if (_autoResolveAuthorityAndTenant)
+ {
+ _logger.LogDebug("Resolving authority and tenant for {0}", instanceUrl);
+ using var httpClient = new System.Net.Http.HttpClient();
+ Auth.AuthorityResolver authorityResolver = new Auth.AuthorityResolver(httpClient);
+ var authDetails = await authorityResolver.ProbeForExpectedAuthentication(instanceUrl).ConfigureAwait(false);
+ resourceUri = authDetails.Resource;
+ _credentialOptions.AuthorityHost = authDetails.Authority;
+ //_credentialOptions.TenantId = authDetails.Authority.Segments[1].Replace("/", "");
+
+ _logger.LogDebug("Authority and tenant resolved in {0}ms", sw.ElapsedMilliseconds);
+ _logger.LogDebug("Initialize Creds - found authority with name " + (string.IsNullOrEmpty(authDetails.Authority.ToString()) ? "" : authDetails.Authority.ToString()));
+ _logger.LogDebug("Initialize Creds - found resource with name " + (string.IsNullOrEmpty(authDetails.Resource.ToString()) ? "" : authDetails.Resource.ToString()));
+ //_logger.LogDebug("Initialize Creds - found tenantId " + (string.IsNullOrEmpty(_credentialOptions.TenantId) ? "" : _credentialOptions.TenantId));
+ }
+
+ _pipelineCredential = new AzurePipelinesCredential(_tenantId, _clientId, _serviceConnectionId, Environment.GetEnvironmentVariable(_systemAccessTokenEnvVarName), _credentialOptions);
+
+ _logger.LogDebug("Credentials initialized in {0}ms", sw.ElapsedMilliseconds);
+ sw.Start();
+
+ return resourceUri;
+ }
+
+ }
+}
diff --git a/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/BuildDrop.ps1 b/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/BuildDrop.ps1
new file mode 100644
index 0000000..851b3ca
--- /dev/null
+++ b/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/BuildDrop.ps1
@@ -0,0 +1,120 @@
+#Build Drop Script
+#Drops the files in the drop folder for the build.
+[CmdletBinding(PositionalBinding=$true)]
+param(
+ [string] $BuildSourcesDirectory ,
+ [string] $BuildConfiguration,
+ [string] $StagingDirectory,
+ [string] $ProjectRootDirectory,
+ [string] $SolutionName,
+ [bool] $RunFromVSBuild = $false,
+ [bool] $EnableDebug = $false
+
+ )
+
+#BuildDrop for Microsoft.Xrm.OnlineManagementAPI solution
+Write-Host ">>> ========================= Invoking BuildDrop.ps1 for $SolutionName ======================="
+Write-Host ">>> BuildSourcesDirectory = $BuildSourcesDirectory"
+Write-Host ">>> ProjectRootDirectory = $ProjectRootDirectory"
+Write-Host ">>> SolutionName = $SolutionName"
+Write-Host ">>> BuildConfiguration = $BuildConfiguration"
+Write-Host ">>> StagingDirectory = $StagingDirectory"
+Write-Host ">>> RunFromVSBuild = $RunFromVSBuild"
+Write-Host ">>> Write Debug Info = $EnableDebug"
+
+
+if ( [System.String]::IsNullOrEmpty($StagingDirectory) -eq $true)
+{
+ #Running local build
+ $StagingDirectory = $BuildSourcesDirectory
+}
+
+if ( $RunFromVSBuild -eq $false )
+{
+ $dropFolderName = "Drop"
+}
+else
+{
+ $dropFolderName = "Drop"
+}
+
+$SolutionName = "Microsoft.PowerPlatform.Dataverse.Client.PowerShell";
+#Create path for drop directory
+#format for Local Build: Root/Drop/Buildconfig/SolutionName/Bins.
+#format for Server Build: Root/Buildconfig/SolutionName/Bins.
+if($RunFromVSBuild -eq $false)
+{
+ #$dropPath = [System.IO.Path]::Combine($dropFolderName , $SolutionName )
+ $dropPath = [System.IO.Path]::Combine($dropFolderName )
+}
+else
+{
+ $dropPath = [System.IO.Path]::Combine($StagingDirectory , $dropFolderName , $BuildConfiguration)
+}
+Write-Host ">>> Output path is $dropPath"
+
+## Assembly Out directory
+if($RunFromVSBuild -eq $false)
+{
+ $BinsDirectory = $StagingDirectory #[System.IO.Path]::Combine($ProjectRootDirectory , $SolutionName , "bin" , $BuildConfiguration )
+}
+else
+{
+ $BinsDirectory = [System.IO.Path]::Combine($BuildSourcesDirectory , "bin" , $BuildConfiguration, "DataverseClient" , "net6.0" )
+}
+## Copying PowerShell Module out only.
+Write-Host ">>> BINS path is $BinsDirectory"
+
+# Setup Module Drop Directory Key
+if($RunFromVSBuild -eq $false)
+{
+ $PowerShellModuleFilesDirectory = [System.IO.Path]::Combine($dropPath , $SolutionName)
+}
+else {
+ $PowerShellModuleFilesDirectory = [System.IO.Path]::Combine($dropPath , $SolutionName)
+}
+Write-Host ">>> Module Drop path is $PowerShellModuleFilesDirectory"
+
+
+## ############## Project or Solution COPY code here. ############ ##
+#create the Root Drop directory
+New-Item -ItemType directory -Force $dropPath
+##create subfolder for Management Powershell
+New-Item -ItemType Directory -Force $PowerShellModuleFilesDirectory
+
+if ( [System.IO.Directory]::Exists($PowerShellModuleFilesDirectory) -eq $true )
+{
+ #copy launcher.
+ #Copy-Item -Path "$BinsDirectory\*" -Destination $dropFolderName -Include 'RegisterXrmTooling.ps1' -Force
+ Robocopy $BinsDirectory $dropPath 'RegisterServiceClient.ps1' /XX
+ if ($lastexitcode -le 7) {
+ Write-Host ">>> ExitCode = " $lastexitcode
+ $lastexitcode = 0
+ }
+
+ # remove anything from Target so as to not upset robocopy
+
+ #copy modules.
+ Robocopy ([System.IO.Path]::Combine($BinsDirectory , 'Microsoft.PowerPlatform.Dataverse.Client.PowerShell')) $PowerShellModuleFilesDirectory *.* /XX
+ if ($lastexitcode -le 7) {
+ Write-Host ">>> ExitCode = " $lastexitcode
+ $lastexitcode = 0
+ }
+ #copy DLL's
+ Robocopy $BinsDirectory $PowerShellModuleFilesDirectory *.dll
+ if ($lastexitcode -le 7)
+ {
+ Write-Host ">>> ExitCode1 = " $lastexitcode
+ $lastexitcode = 0
+ }
+ #copy Help
+ Robocopy $BinsDirectory $PowerShellModuleFilesDirectory *.dll-help.xml
+ if ($lastexitcode -le 7)
+ {
+ Write-Host ">>> ExitCode1 = " $lastexitcode
+ $lastexitcode = 0
+ Exit 0
+ }
+
+
+}
diff --git a/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/Commands/BaseCmdlet.cs b/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/Commands/BaseCmdlet.cs
new file mode 100644
index 0000000..d20acb2
--- /dev/null
+++ b/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/Commands/BaseCmdlet.cs
@@ -0,0 +1,159 @@
+// Ignore Spelling: Dataverse
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using System.Management.Automation;
+using System.Reflection;
+
+namespace Microsoft.PowerPlatform.Dataverse.Client.PowerShell.Commands
+{
+ public class BaseCmdLet : PSCmdlet
+ {
+ #region Vars
+ /////
+ ///// file Writer Link
+ /////
+ //private Microsoft.Xrm.Tooling.Connector.DynamicsFileLogTraceListener commonFileWriter = null;
+
+ ///
+ /// when present and populated, this will write the logs to the directory specified. loges are written only when -verbose is chosen.
+ ///
+ [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)]
+ public string LogWriteDirectory { get; set; } = string.Empty;
+
+//#if DEBUG
+// private System.Diagnostics.DefaultTraceListener commonConsoleListener = null;
+//#endif
+
+ #endregion
+
+ internal ILogger CreateILogger()
+ {
+ var ConfigFileLocation = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "", "appsettings.json");
+ IConfiguration config = new ConfigurationBuilder()
+ .AddJsonFile(ConfigFileLocation, optional: true, reloadOnChange: true)
+ .Build();
+
+ ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
+ builder.AddConsole(options =>
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
+ options.IncludeScopes = true;
+ options.TimestampFormat = "hh:mm:ss ";
+#pragma warning restore CS0618 // Type or member is obsolete
+ })
+ .AddConfiguration(config.GetSection("Logging")));
+
+ return loggerFactory.CreateLogger();
+ }
+
+ ///
+ /// Determines if its necessary to enable tracing.
+ ///
+ internal void SetDiagnosticsMode()
+ {
+ if (this.MyInvocation.BoundParameters.ContainsKey("verbose") && (SwitchParameter)this.MyInvocation.BoundParameters["verbose"])
+ {
+// CrmConnectControl.Utility.TraceControlSettings.TraceLevel = System.Diagnostics.SourceLevels.Verbose;
+// Connector.TraceControlSettings.TraceLevel = System.Diagnostics.SourceLevels.Verbose;
+
+// if (CrmConnectControl.Utility.TraceControlSettings.TraceLevel != System.Diagnostics.SourceLevels.Off)
+// {
+// // Create a common TraceFile Writer.
+// if (commonFileWriter == null)
+// {
+// if (!string.IsNullOrEmpty(LogWriteDirectory) && System.IO.Directory.Exists(LogWriteDirectory))
+// {
+// commonFileWriter = new Connector.DynamicsFileLogTraceListener()
+// {
+// BaseFileName = "Microsoft.PowerPlatform.Dataverse.Client.PowerShell",
+// Location = VisualBasic.Logging.LogFileLocation.Custom,
+// CustomLocation = LogWriteDirectory
+// };
+// }
+// else
+// {
+// commonFileWriter = new Connector.DynamicsFileLogTraceListener()
+// {
+// BaseFileName = "Microsoft.PowerPlatform.Dataverse.Client.PowerShell",
+// Location = VisualBasic.Logging.LogFileLocation.LocalUserApplicationDirectory
+// };
+
+// }
+
+// this.WriteVerbose(string.Format("Verbose output log file: '{0}'", commonFileWriter.FullLogFileName));
+
+// CrmConnectControl.Utility.TraceControlSettings.AddTraceListener(commonFileWriter);
+// Connector.TraceControlSettings.AddTraceListener(commonFileWriter);
+// }
+
+//#if DEBUG
+// if ( commonConsoleListener == null )
+// {
+// commonConsoleListener = new System.Diagnostics.DefaultTraceListener();
+// CrmConnectControl.Utility.TraceControlSettings.AddTraceListener(commonFileWriter);
+// Connector.TraceControlSettings.AddTraceListener(commonFileWriter);
+// }
+//#endif
+
+// }
+ }
+ else
+ if (this.MyInvocation.BoundParameters.ContainsKey("verbose") && !(SwitchParameter)this.MyInvocation.BoundParameters["verbose"])
+ {
+ //// forces it off.
+ //CrmConnectControl.Utility.TraceControlSettings.TraceLevel = System.Diagnostics.SourceLevels.Off;
+ //Connector.TraceControlSettings.TraceLevel = System.Diagnostics.SourceLevels.Off;
+ }
+ }
+
+ ///
+ /// Cleans up open TraceWriters
+ ///
+ internal void CleanUpDiagnosticsMode()
+ {
+ //CrmConnectControl.Utility.TraceControlSettings.CloseListeners();
+ //Connector.TraceControlSettings.CloseListeners();
+ }
+
+ }
+
+ #region Threading Support Class
+ ///
+ /// Type of write to use.
+ ///
+ internal enum WriteInfoType
+ {
+ Warning = 0,
+ Verbose,
+ Debug
+ }
+
+ ///
+ /// holder class to signal handling to the cmdlet adapter writer.
+ ///
+ internal class GeneralWriteInfo
+ {
+ ///
+ /// Warning message to write.
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// Type of Message in this class.
+ ///
+ public WriteInfoType MessageType { get; set; }
+
+ ///
+ /// Warning Message to write.
+ ///
+ ///
+ public GeneralWriteInfo(string warningMessage, WriteInfoType messageType)
+ {
+ Message = warningMessage;
+ MessageType = messageType;
+ }
+ }
+
+ #endregion
+}
diff --git a/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/Commands/CommonAuth.cs b/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/Commands/CommonAuth.cs
new file mode 100644
index 0000000..7348601
--- /dev/null
+++ b/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/Commands/CommonAuth.cs
@@ -0,0 +1,224 @@
+// Ignore Spelling: Dataverse Auth queryfor Orgs
+
+using System.Management.Automation;
+using System.Globalization;
+using Microsoft.Extensions.Logging;
+using Microsoft.PowerPlatform.Dataverse.Client.Model;
+
+namespace Microsoft.PowerPlatform.Dataverse.Client.PowerShell.Commands
+{
+ public class CommonAuth : BaseCmdLet
+ {
+ #region Vars
+ ///
+ /// Connection timeout setting for CRM connection.
+ ///
+ private int maxcrmconnectiontimeoutminutes = -1;
+ ///
+ /// Connection string to use when connecting to CRM
+ ///
+ internal string connectionString = string.Empty;
+
+ ///
+ /// Url of organization to connect too.
+ ///
+ internal Uri? orgUrlToUse;
+ ///
+ /// Tenant ID for FIC
+ ///
+ internal Guid tenantId;
+ ///
+ /// FIC Client Id
+ ///
+ internal Guid clientId;
+ ///
+ /// Service Connection Id
+ ///
+ internal Guid serviceConnectionId;
+ ///
+ /// Environment AccessToken KeyName.
+ ///
+ internal string accessTokenEnvKeyName = string.Empty;
+
+ ///
+ /// CrmSvcConnection
+ ///
+ protected ServiceClient? serviceClient = null;
+
+ /////
+ ///// Progress Record.
+ /////
+ //private ProgressRecord? connProgress = null;
+
+ ///
+ /// Error Record if any.
+ ///
+ private ErrorRecord? errorRecord = null;
+
+ ///
+ /// set when querying for orgs.
+ ///
+ public bool queryforOrgs = false;
+
+ ///
+ /// PowerShell MultiThreaded Adapter.
+ ///
+ //private PowerShellAdapter? adapter = null;
+
+ ///
+ /// Percentage completed.
+ ///
+ //private int percentCmp = 0;
+
+
+ #endregion
+
+ #region Properties.
+
+ ///
+ /// The following is the definition of the input parameter "MaxConnectionTimeOutMinutes".
+ /// User credential used to login to CRM
+ ///
+ [Parameter(Position = 20, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
+ public int MaxConnectionTimeOutMinutes
+ {
+ get { return this.maxcrmconnectiontimeoutminutes; }
+ set { this.maxcrmconnectiontimeoutminutes = value; }
+ }
+
+ #endregion
+
+ public CommonAuth()
+ {
+ // Auto Add TLS 1.2 support which is required.
+ System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
+ }
+
+ ///
+ /// Get Orgs.
+ ///
+ public void ExecuteGetOrgs()
+ {
+ ExecuteAuth();
+ if (errorRecord != null)
+ WriteError(errorRecord);
+ }
+
+ ///
+ /// Run Auth.
+ ///
+ public void ExecuteAuth()
+ {
+ RunAuth();
+ if (!queryforOrgs && errorRecord != null)
+ WriteError(errorRecord);
+ }
+
+ ///
+ /// Authenticate with Dataverse.
+ ///
+ private void RunAuth()
+ {
+ if (serviceConnectionId != null && serviceConnectionId != Guid.Empty)
+ {
+ serviceClient = AzPipelineFederatedIdentityAuth.CreateServiceClient(
+ tenantId.ToString(),
+ clientId.ToString(),
+ serviceConnectionId.ToString(),
+ new ConnectionOptions()
+ {
+ ServiceUri = orgUrlToUse,
+ Logger = CreateILogger()
+ }
+ );
+ }
+ else if (!string.IsNullOrEmpty(connectionString))
+ {
+ // Connection string is present.
+ ServiceClient localServiceClient = new ServiceClient(connectionString, CreateILogger());
+ if (localServiceClient != null && localServiceClient.IsReady)
+ serviceClient = localServiceClient;
+ else
+ {
+ if (!string.IsNullOrEmpty(localServiceClient?.LastError) || localServiceClient?.LastException != null)
+ {
+ errorRecord = new ErrorRecord(new Exception(string.Format(CultureInfo.InvariantCulture, "Failed to connect to Dataverse: {0}", localServiceClient.LastError), localServiceClient.LastException), "-10", ErrorCategory.PermissionDenied, null);
+ WriteError(errorRecord);
+ serviceClient = null;
+ }
+ }
+ }
+ else
+ {
+ // No connection string.
+ errorRecord = new ErrorRecord(new Exception("Connection string is required to connect to Dataverse"), "-1", ErrorCategory.InvalidArgument, null);
+ WriteError(errorRecord);
+ serviceClient = null;
+ }
+
+ // Set connection timeout if required.
+ if (serviceClient != null && MaxConnectionTimeOutMinutes != -1)
+ {
+ WriteVerbose(string.Format(CultureInfo.InstalledUICulture, "Dataverse Connection Timeout set to {0} Minutes", MaxConnectionTimeOutMinutes));
+ SetConnectionTimeoutValues(new TimeSpan(0, MaxConnectionTimeOutMinutes, 0));
+ }
+ else
+ if (serviceClient != null)
+ WriteVerbose(string.Format(CultureInfo.InstalledUICulture, "Dataverse Connection Timeout is set to {0} Minutes", GetConnectionTimeoutValues().Minutes));
+ }
+
+ #region Private classes.
+
+ ///
+ /// Updates the timeout value to extend the amount of item that a request will wait.
+ ///
+ public void SetConnectionTimeoutValues(TimeSpan TimeOutToSet)
+ {
+ ServiceClient.MaxConnectionTimeout = TimeOutToSet;
+ }
+
+ ///
+ /// Gets the current connection time out value.
+ ///
+ ///
+ private TimeSpan GetConnectionTimeoutValues()
+ {
+ return ServiceClient.MaxConnectionTimeout;
+ }
+
+
+ #endregion
+
+ }
+ #region Extension Methods for SecureString
+ /////
+ ///// Adds a extension to Secure string
+ /////
+ //internal static class SecureStringExtensions
+ //{
+ // ///
+ // /// DeCrypt a Secure password
+ // ///
+ // ///
+ // ///
+ // public static string ToUnsecureString(this SecureString value)
+ // {
+ // if (null == value)
+ // throw new ArgumentNullException("value");
+
+ // // Get a pointer to the secure string memory data.
+ // IntPtr ptr = Marshal.SecureStringToGlobalAllocUnicode(value);
+ // try
+ // {
+ // // DeCrypt
+ // return Marshal.PtrToStringUni(ptr);
+ // }
+ // finally
+ // {
+ // // release the pointer.
+ // Marshal.ZeroFreeGlobalAllocUnicode(ptr);
+ // }
+ // }
+ //}
+ #endregion
+}
diff --git a/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/Commands/GetCrmConnection.cs b/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/Commands/GetCrmConnection.cs
new file mode 100644
index 0000000..5b20404
--- /dev/null
+++ b/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/Commands/GetCrmConnection.cs
@@ -0,0 +1,134 @@
+// Ignore Spelling: Dataverse
+
+using System.Management.Automation;
+using System.Reflection;
+
+namespace Microsoft.PowerPlatform.Dataverse.Client.PowerShell.Commands
+{
+ ///
+ /// this will establish a CRM connection
+ ///
+ [Cmdlet(VerbsCommon.Get, "PowerPlatformConnection")]
+ public class GetPowerPlatformConnection : CommonAuth
+ {
+
+ ///
+ /// Tenant Id of the FIC
+ ///
+ [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "FIC")]
+ public Guid TenantId
+ {
+ get { return base.tenantId; }
+ set { base.tenantId = value; }
+ }
+
+ ///
+ /// Client Id of the FIC
+ ///
+ [Parameter(Mandatory = true, Position = 2, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "FIC")]
+ public Guid ClientId
+ {
+ get { return base.clientId; }
+ set { base.clientId = value; }
+ }
+
+ ///
+ /// Service Connection Id of the FIC
+ ///
+ [Parameter(Mandatory = true, Position = 3, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "FIC")]
+ public Guid ServiceConnectionId
+ {
+ get { return base.serviceConnectionId; }
+ set { base.serviceConnectionId = value; }
+ }
+
+ ///
+ /// OrganizationUrl of the FIC
+ ///
+ [Parameter(Mandatory = true, Position = 4, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "FIC")]
+ public Uri? OrganizationUrl
+ {
+ get { return base.orgUrlToUse; }
+ set { base.orgUrlToUse = value; }
+ }
+
+ ///
+ /// Environment AccessToken KeyName.
+ ///
+ [Parameter(Mandatory = false , Position = 4, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "FIC")]
+ public string AccessTokenEnvKeyName
+ {
+ get { return base.accessTokenEnvKeyName; }
+ set { base.accessTokenEnvKeyName = value; }
+ }
+
+ ///
+ /// Used to set the connection string to connect to crm. all other connection elements are ignored when this string appears.
+ ///
+ [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ConnectionStringOnly")]
+ public string ConnectionString
+ {
+ get { return base.connectionString; }
+ set { base.connectionString = value; }
+ }
+
+ ///
+ /// PreInitializion
+ ///
+ protected override void BeginProcessing()
+ {
+ base.queryforOrgs = false;
+
+ //string thisAssemblyPath = string.Empty;
+ //var AsmList = AppDomain.CurrentDomain.GetAssemblies();
+ //bool found = false;
+ //// Look in the assemblies for the resources file.
+ //foreach (Assembly asm in AsmList)
+ //{
+ // if (asm.FullName.ToLower().Contains(XamlResourceName))
+ // {
+ // found = true;
+ // break;
+ // }
+ //}
+
+ //if (System.IO.File.Exists(Assembly.GetExecutingAssembly().Location))
+ // thisAssemblyPath = Assembly.GetExecutingAssembly().Location;
+
+ //if (!found)
+ //{
+ // // Get the Direction Info object
+ // System.IO.DirectoryInfo d = new System.IO.DirectoryInfo(thisAssemblyPath);
+ // // Remove file name
+ // thisAssemblyPath = System.IO.Path.GetDirectoryName(thisAssemblyPath);
+
+ // // Load the assembly.
+ // if (System.IO.File.Exists(System.IO.Path.Combine(thisAssemblyPath, string.Format("{0}.dll", XamlResourceName))))
+ // Assembly.LoadFile(System.IO.Path.Combine(thisAssemblyPath, string.Format("{0}.dll", XamlResourceName)));
+ //}
+
+ base.BeginProcessing();
+ }
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ base.SetDiagnosticsMode();
+ base.ExecuteAuth();
+ if (serviceClient != null)
+ WriteObject(serviceClient);
+ base.CleanUpDiagnosticsMode();
+ }
+ catch (Exception generalEx)
+ {
+ // General error write for something we don't understand going wrong.
+ WriteError(new ErrorRecord(generalEx, "-9", ErrorCategory.SyntaxError, null));
+ }
+ }
+
+ }//End Class
+}//End namespace
diff --git a/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/Commands/PowerShellAdapter.cs b/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/Commands/PowerShellAdapter.cs
new file mode 100644
index 0000000..d40d8b8
--- /dev/null
+++ b/src/GeneralTools/DataverseClient/PowerShell/Microsoft.PowerPlatform.Dataverse.Client.PowerShell/Commands/PowerShellAdapter.cs
@@ -0,0 +1,110 @@
+// Ignore Spelling: Dataverse cmdlet
+
+using System.Management.Automation;
+
+namespace Microsoft.PowerPlatform.Dataverse.Client.PowerShell.Commands
+{
+ ///
+ /// Adapter Class
+ ///
+ internal class PowerShellAdapter
+ {
+ private ManualResetEvent queueEvent = new ManualResetEvent(false);
+
+ #region Parameters
+ private Cmdlet cmdlet { get; set; }
+ private Queue
-
+
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
-
+
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
@@ -1961,22 +2095,22 @@
Relationship Name
Entities to associate
-
+
Associate an entity with a set of entities
+ Propagates notification that operations should be canceled.
-
+
Associate an entity with a set of entities
- Propagates notification that operations should be canceled.
@@ -2004,50 +2138,51 @@
Entity to create
ID of newly created entity
-
+
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.
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
-
+
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
+ Propagates notification that operations should be canceled.
-
+
Delete instance of an entity
Logical name of entity
Id of entity
- Propagates notification that operations should be canceled.
@@ -2057,22 +2192,29 @@
Relationship Name
Entities to disassociate
-
+
Disassociate an entity with a set of entities
+ Propagates notification that operations should be canceled.
-
+
Disassociate an entity with a set of entities
- Propagates notification that operations should be canceled.
+
+
+
+
+
+
+
@@ -2083,13 +2225,6 @@
Logging provider
-
-
-
-
-
-
-
Discovers Organizations Using the global discovery service.
@@ -2147,17 +2282,17 @@
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.
+ Propagates notification that operations should be canceled.
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
@@ -2228,21 +2363,21 @@
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
+ Propagates notification that operations should be canceled.
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
@@ -2251,17 +2386,17 @@
Query to Request
EntityCollection Result
-
+
Retrieves a collection of entities
+ Propagates notification that operations should be canceled.
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
@@ -2269,16 +2404,16 @@
Issues an update to Dataverse.
Entity to update into Dataverse
-
+
Updates an entity and process any related entities
entity to update
+ Propagates notification that operations should be canceled.
-
+
Updates an entity and process any related entities
entity to update
- Propagates notification that operations should be canceled.
@@ -2512,10 +2647,12 @@
Logg an error with an Exception
-
+
- Log a Message as an Information event.
+ Log a Trace event
+
+
@@ -2523,12 +2660,10 @@
-
+
- Log a Trace event
+ Log a Message as an Information event.
-
-
@@ -2607,11 +2742,6 @@
-
-
- Creates a CdsService Client Exception
- Error Message
-
Creates a CdsService Client Exception
@@ -2625,6 +2755,11 @@
+
+
+ Creates a CdsService Client Exception
+ Error Message
+
@@ -2638,11 +2773,6 @@
-
-
- Creates a CdsService Client Exception
- Error Message
-
Creates a CdsService Client Exception
@@ -2658,6 +2788,11 @@
Data Properties
+
+
+ Creates a CdsService Client Exception
+ Error Message
+
Creates a CdsService Client Exception from a httpOperationResult.
@@ -2683,20 +2818,20 @@
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.
+ 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.
- Loads the given XML into the XmlDocument.
- This overload is useful when a whitespace only element value is valid content.
+ Loads the given XML into the XmlDocument.
The XML to load.
- Whether the whitespaces are to be preserved or not.
the new XmlDocument object
@@ -2711,17 +2846,17 @@
Xml stream.
The new XmlReader object.
-
+
- Creates an XmlReader object with secure default property values.
+ 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 XmlReader object with secure default property values and given whitespace setting.
+ Creates an XmlReader object with secure default property values.
The string to get the data from.
- Whether the whitespaces are to be preserved or not.
the new XmlReader object
diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.AzAuth.nuspec b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.AzAuth.nuspec
index 274ac43..ba9b207 100644
--- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.AzAuth.nuspec
+++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.AzAuth.nuspec
@@ -15,32 +15,32 @@
Dynamics CommonDataService CDS PowerApps PowerPlatform ServiceClient Dataverse
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt
index 75bb23c..d8a2833 100644
--- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt
+++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt
@@ -7,9 +7,12 @@ Notice:
Note: Only AD on FullFramework, OAuth, Certificate, ClientSecret Authentication types are supported at this time.
++CURRENTRELEASEID++
+Fix for endless retry loop issue in WebAPI calls when specific error states are encountered.
Fix for Logging MSAL telemetry when using ILogger
Previously, Logs for MSAL were not written to the configured ILogger, they would only go to Trace Source and InMemory Logs.
Fix for RequestBuilder to properly honor CrmUserId and AADOid in request builder requests.
+Fix for ForceServerMetadataCacheConsistency not being effective until an operation to retrieve current organization version has been executed.
+ If this us set to a value before any organization detail related information is retrieved, it will now cause the organization info to be retrieved from Dataverse. This is done only once.
Updated ServiceClient retry logic to use the server specified RetryAfter for Time and Concurrency throttling fault codes, in addition to Burst.
Updated ConnectionService retry logic to parse RetryAfter header as seconds instead of hours.
Dependency Changes:
diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.nuspec b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.nuspec
index f0c0196..8adbec8 100644
--- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.nuspec
+++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.nuspec
@@ -24,6 +24,9 @@
+
+
+
@@ -35,6 +38,8 @@
+
+
@@ -46,6 +51,8 @@
+
+
@@ -59,8 +66,8 @@
-
-
+
+