I use Hyper-V for all of my development work. Up until now I've been providing internet access to my Hyper-V images by setting up a Virtual Switch in Hyper-V, and configuring it as an Internal network. When my host is connected to the internet, I use Internet Connection Sharing to this Virtual Switch. I then configure the Hyper-V images to use a legacy adapter and select this Virtual Switch. Here is how my WiFi adapter is shared.
This procedure is well documented in other posts. But it has a snag. Internet Connection Sharing insists on using IP addresses in the 192.168.0.* range and your WiFi router must use an alternative IP address range e.g. 192.168.1.*
I recently upgraded my Virgin broadband and the new router was using the IP range 192.168.0.* You could supposedly change it but I could not get this to work properly so I started to hunt around for another solution that would leave the IP range unchanged. I stumbled on the wonderful network bridge.
The solution is the go into Virtual Switch settings and create a new Virtual Switch but select External network. I selected my WiFi network card from the list and enabled "Allow management operating system to share this network adapter".
When you click OK, it takes a minute but what its doing is creating a Network Bridge in your Network Connections along with a Hyper-V Virtual Ethernet adapter you are probably familiar with. My virtual machines use the same approach, a legacy adapter that is configured for this external Virtual Switch.
Now when I look at my Network Connections its a bit odd. My WiFi adapter displays Enabled, Bridged even when I am not connected to the Internet. When I make the connection in the usual way it is the Hyper-V Virtual adapter that goes through the whole Identifying... stage until it connects. It looks counter-intuitive but it works a treat. So that's it. My Hyper-V images are connected to the Internet and I didn't have to change the IP address of the router.
Friday, April 22, 2016
Friday, April 15, 2016
Dynamics CRM 2016 and Use Legacy form rendering
So you may know that Microsoft has improved form load by doing some extensive caching. There is a setting under General Settings called "Use Legacy form rendering" which you can set to Yes or No.
Now you might wonder why I am writing a post about this setting. Well it was a JavaScript error on a Form Load that I was getting. I had my own JavaScript code on the Form Load but the error shown was confusing:
Field:window
Event:onload
Error:Unable to get property '$o_3' of undefined or null reference
Huh? A bit of Googling showed that others had come across it to
The post included this comment "Note that this error only occurs when you turn on the “Use Legacy form rendering”. Sure enough changing this setting to No avoids the error.
But what if you want to preserve the faster form rendering? Patric provide the solution in his blog.
Add the following line before calling the prefilter function.
Xrm.Page.getControl(“EntityName”)._control && Xrm.Page.getControl(“EntityName”)._control.tryCompleteOnDemandInitialization && Xrm.Page.getControl(“EntityName”)._control.tryCompleteOnDemandInitialization();
Brilliant.
Now you might wonder why I am writing a post about this setting. Well it was a JavaScript error on a Form Load that I was getting. I had my own JavaScript code on the Form Load but the error shown was confusing:
Field:window
Event:onload
Error:Unable to get property '$o_3' of undefined or null reference
Huh? A bit of Googling showed that others had come across it to
The post included this comment "Note that this error only occurs when you turn on the “Use Legacy form rendering”. Sure enough changing this setting to No avoids the error.
But what if you want to preserve the faster form rendering? Patric provide the solution in his blog.
Add the following line before calling the prefilter function.
Xrm.Page.getControl(“EntityName”)._control && Xrm.Page.getControl(“EntityName”)._control.tryCompleteOnDemandInitialization && Xrm.Page.getControl(“EntityName”)._control.tryCompleteOnDemandInitialization();
Brilliant.
Dynamics CRM 2016 and Legacy Entities
This is not the first time I've run into a problem with so called "Legacy Entities". I am referring to the entities that have been left out in the cold since CRM 2013. These include
There are some things that these entities don't support which include
I have also come across with problems when upgrading to CRM 2016. This problem occurred on Opportunity Products form which had been customised in CRM 2015. I removed a number of fields I didn't need and it was working fine.
After the upgrade to 2016 though I was getting this JavaScript error on form load
Field:window
Event:onload
Error:Unable to get property 'addOnChange' of undefined or null reference
Now that was not from any JavaScript code that I had but from a Microsoft library called OpportunityProduct_main_system_library.js
When I looked into the code I could see that it was referring to attributes that were not on the form:
Manual discount amount
Tax
When I added these back onto the form and hid them, the error went away.
Moral of the story: Don't delete attributes from legacy forms - just hide them.
- address
- opportunity product
- quote product
- order product
- invoice product
There are some things that these entities don't support which include
- Accessible in the Tablet App
- Update attributes with workflows
- Business Rules (though oddly Opportunity product does support business rules)
- Old style forms
I have also come across with problems when upgrading to CRM 2016. This problem occurred on Opportunity Products form which had been customised in CRM 2015. I removed a number of fields I didn't need and it was working fine.
After the upgrade to 2016 though I was getting this JavaScript error on form load
Field:window
Event:onload
Error:Unable to get property 'addOnChange' of undefined or null reference
Now that was not from any JavaScript code that I had but from a Microsoft library called OpportunityProduct_main_system_library.js
When I looked into the code I could see that it was referring to attributes that were not on the form:
Manual discount amount
Tax
When I added these back onto the form and hid them, the error went away.
Moral of the story: Don't delete attributes from legacy forms - just hide them.
Saturday, December 12, 2015
Dependent (Linked) OptionSets in Dynamics CRM 2015
I recently used the sample code provided in the SDK that allows you to have multiple OptionSets linked together so that selecting an item in the first OptionSet will filter the items appearing in the second.
https://msdn.microsoft.com/en-us/library/gg594433(v=crm.7).aspx
There was an error in the instructions for configuring the OnLoad event on the form. It says to use
"sample_TicketDependentOptionSetConfig.xml"
as a parameter. That's wrong. You need to use
"sample_TicketDependentOptionSetConfig"
The SDK.DependentOptionSet.init function in the JavaScript file I modified slightly to support multiple languages. The first few lines now read
//Retrieve the XML Web Resource specified by the parameter passed var clientURL = Xrm.Page.context.getClientUrl();
var userLcid = Xrm.Page.context.getUserLcid();
var pathToWR = clientURL + "/WebResources/" + webResourceName + "_" + userLcid;
The data files were then appended with the relevant LCID for each language
e.g. "sample_TicketDependentOptionSetConfig_1033.xml"
A very cool and easy to use JavaScript Library. Thanks Microsoft.
https://msdn.microsoft.com/en-us/library/gg594433(v=crm.7).aspx
There was an error in the instructions for configuring the OnLoad event on the form. It says to use
"sample_TicketDependentOptionSetConfig.xml"
as a parameter. That's wrong. You need to use
"sample_TicketDependentOptionSetConfig"
The SDK.DependentOptionSet.init function in the JavaScript file I modified slightly to support multiple languages. The first few lines now read
//Retrieve the XML Web Resource specified by the parameter passed var clientURL = Xrm.Page.context.getClientUrl();
var userLcid = Xrm.Page.context.getUserLcid();
var pathToWR = clientURL + "/WebResources/" + webResourceName + "_" + userLcid;
The data files were then appended with the relevant LCID for each language
e.g. "sample_TicketDependentOptionSetConfig_1033.xml"
A very cool and easy to use JavaScript Library. Thanks Microsoft.
Labels:
Dynamics CRM 2015,
JavaScript,
Linked Option Sets,
OptionsSets
Sunday, November 29, 2015
ILMerge Command Line Syntax
I had cause the other day to use ILMerge to combine several DLLs. The first issue I came across was:
Unresolved assembly reference not allowed: System.Core.
This is mentioned in several blogs and the solution suggested was to use the /lib switch and specify the path to the .NET library you want for the target. In my case I wanted to target 4.5.2 so I used the following which supposedly worked (the blog said)
ilmerge /lib:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2" /out:MERGED.First.dll First.dll Second.dll /keyfile:key.snk
Note that if you strongly signed your First DLL don't assume the output will be strongly signed because it won't be. You have to add the /keyfile option to do that.
Note also there are blogs that say don't try and point to C:\Windows... because that is not the correct location. You need to use the Reference Assemblies path given above.
Well I didn't get the error and ILMerge started off and I waited. And waited. And waited. So basically ILMerge just hangs still consuming by the way 50% CPU just to fool you into thinking its actually doing something.
Its the wrong syntax. The correct syntax uses /targetplatform not /lib
ilmerge /targetplatform:v4,"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2" /out:MERGED.First.dll First.dll Second.dll /keyfile:key.snk
Unresolved assembly reference not allowed: System.Core.
This is mentioned in several blogs and the solution suggested was to use the /lib switch and specify the path to the .NET library you want for the target. In my case I wanted to target 4.5.2 so I used the following which supposedly worked (the blog said)
ilmerge /lib:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2" /out:MERGED.First.dll First.dll Second.dll /keyfile:key.snk
Note that if you strongly signed your First DLL don't assume the output will be strongly signed because it won't be. You have to add the /keyfile option to do that.
Note also there are blogs that say don't try and point to C:\Windows... because that is not the correct location. You need to use the Reference Assemblies path given above.
Well I didn't get the error and ILMerge started off and I waited. And waited. And waited. So basically ILMerge just hangs still consuming by the way 50% CPU just to fool you into thinking its actually doing something.
Its the wrong syntax. The correct syntax uses /targetplatform not /lib
ilmerge /targetplatform:v4,"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2" /out:MERGED.First.dll First.dll Second.dll /keyfile:key.snk
Creating a SharePoint OnLine Folder using CSOM
Surprisingly I can't find a complete solution to this on the blogosphere. So let me put that right.
First add two references to
Microsoft.SharePoint.Client.dll
Microsoft.SharePoint.Client.RunTime.dll
I found them on my 64-bit server located here
C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI
In your code add a using statement
using Microsoft.SharePoint.Client;
Then in your method add the following
using (ClientContext ctx = new ClientContext(site))
{var securePassword = new SecureString();
foreach (char c in password)
{
securePassword.AppendChar(c);
}
ctx.Credentials = new Microsoft.SharePoint.Client.SharePointOnlineCredentials(username, securePassword);
var list = ctx.Web.Lists.GetByTitle(doclib);
var folderRoot = list.RootFolder;
ctx.Load(folderRoot);
ctx.ExecuteQuery();folderRoot = folderRoot.Folders.Add(folder);
ctx.ExecuteQuery();
};
First add two references to
Microsoft.SharePoint.Client.dll
Microsoft.SharePoint.Client.RunTime.dll
I found them on my 64-bit server located here
C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI
In your code add a using statement
using Microsoft.SharePoint.Client;
Then in your method add the following
using (ClientContext ctx = new ClientContext(site))
{var securePassword = new SecureString();
foreach (char c in password)
{
securePassword.AppendChar(c);
}
ctx.Credentials = new Microsoft.SharePoint.Client.SharePointOnlineCredentials(username, securePassword);
var list = ctx.Web.Lists.GetByTitle(doclib);
var folderRoot = list.RootFolder;
ctx.Load(folderRoot);
ctx.ExecuteQuery();folderRoot = folderRoot.Folders.Add(folder);
ctx.ExecuteQuery();
};
Monday, August 31, 2015
Serialize a CRM Entity to XML in Sandbox mode Plugin
I have been struggling with this problem for a while. Previous posts have outlined the problem: how to get changes in CRM sent to Back Office systems as XML messages. When using CRM Online a big frustration is that you cannot serialize objects to XML because the WriteObject method of the serializer throws a security exception.
While you can post the Context of a plugin to the Azure Service Bus, you have to use the CRM SDK to interpret this and turn it into something useful. My previous post outlines how to do this with an Azure Worker Process.
(You could construct the XML message line by line using string builder but seriously who wants to do that?)
I found a simple solution which may solve the problem. It was this post that provided the inspiration. It describes how to turn FetchXML results into a data table. Now FetchXML returns an entity collection and the code shows how to load that into a data table and it also handles the special CRM data types like Entity Reference.
So there you are in a Sandbox plugin. You have the Post-Image entity and you want to convert that into a nicely structured XML file that you can post to the Azure Service Bus. You can use part of the code above (ignoring the FetchXML query) to convert the Post-Image to a data table. I put it into a function I called ConvertEntityToDataTable.
Now getting XML is easy.
DataSet ds = new DataSet("Invoice");
DataTable dt = new DataTable("Attributes");
ConvertEntityToDataTable(dt, entity);
ds.Tables.Add(dt);
string xml = ds.GetXml();
The result looks like this (I've just given an extract)
<Invoice>
<Attributes>
<billto_city>London</billto_city>
<billto_line1>12 Hgh Street</billto_line1>
<billto_line2>Clapham Common</billto_line2>
<billto_line3>Clapham</billto_line3>
</Attributes>
</Invoice>
Remember that PostImages provide their attributes in alphabetical order which is great for mapping structured XML messages. The Target entity does not so you would need to address that.
The XML is missing some things I would need to add:
(a) the xml declaration at the top specifying the encoding
(b) a namespace to identify the message to an ESB
But I am delighted with the result because now I can construct proper XML messages in a Sandbox plugin.
The two functions required are shown below
///////
private void ConvertEntityToDataTable(DataTable dataTable, Entity entity)
{
DataRow row = dataTable.NewRow();
foreach (var attribute in entity.Attributes)
{
if (!dataTable.Columns.Contains(attribute.Key))
{
dataTable.Columns.Add(attribute.Key);
}
row[attribute.Key] = getAttributeValue(attribute.Value).ToString();
}
foreach (var fv in entity.FormattedValues)
{
if (!dataTable.Columns.Contains(fv.Key + "name"))
{
dataTable.Columns.Add(fv.Key + "name");
}
row[fv.Key + "name"] = fv.Value;
}
dataTable.Rows.Add(row);
}
///////
private object getAttributeValue(object entityValue)
{
object output = "";
switch (entityValue.ToString())
{
case "Microsoft.Xrm.Sdk.EntityReference":
output = ((EntityReference)entityValue).Name;
break;
case "Microsoft.Xrm.Sdk.OptionSetValue":
output = ((OptionSetValue)entityValue).Value.ToString();
break;
case "Microsoft.Xrm.Sdk.Money":
output = ((Money)entityValue).Value.ToString();
break;
case "Microsoft.Xrm.Sdk.AliasedValue":
output = getAttributeValue(((Microsoft.Xrm.Sdk.AliasedValue)entityValue).Value);
break;
default:
output = entityValue.ToString();
break;
}
return output;
}
}
While you can post the Context of a plugin to the Azure Service Bus, you have to use the CRM SDK to interpret this and turn it into something useful. My previous post outlines how to do this with an Azure Worker Process.
(You could construct the XML message line by line using string builder but seriously who wants to do that?)
I found a simple solution which may solve the problem. It was this post that provided the inspiration. It describes how to turn FetchXML results into a data table. Now FetchXML returns an entity collection and the code shows how to load that into a data table and it also handles the special CRM data types like Entity Reference.
So there you are in a Sandbox plugin. You have the Post-Image entity and you want to convert that into a nicely structured XML file that you can post to the Azure Service Bus. You can use part of the code above (ignoring the FetchXML query) to convert the Post-Image to a data table. I put it into a function I called ConvertEntityToDataTable.
Now getting XML is easy.
DataSet ds = new DataSet("Invoice");
DataTable dt = new DataTable("Attributes");
ConvertEntityToDataTable(dt, entity);
ds.Tables.Add(dt);
string xml = ds.GetXml();
The result looks like this (I've just given an extract)
<Invoice>
<Attributes>
<billto_city>London</billto_city>
<billto_line1>12 Hgh Street</billto_line1>
<billto_line2>Clapham Common</billto_line2>
<billto_line3>Clapham</billto_line3>
</Attributes>
</Invoice>
Remember that PostImages provide their attributes in alphabetical order which is great for mapping structured XML messages. The Target entity does not so you would need to address that.
The XML is missing some things I would need to add:
(a) the xml declaration at the top specifying the encoding
(b) a namespace to identify the message to an ESB
But I am delighted with the result because now I can construct proper XML messages in a Sandbox plugin.
The two functions required are shown below
///////
private void ConvertEntityToDataTable(DataTable dataTable, Entity entity)
{
DataRow row = dataTable.NewRow();
foreach (var attribute in entity.Attributes)
{
if (!dataTable.Columns.Contains(attribute.Key))
{
dataTable.Columns.Add(attribute.Key);
}
row[attribute.Key] = getAttributeValue(attribute.Value).ToString();
}
foreach (var fv in entity.FormattedValues)
{
if (!dataTable.Columns.Contains(fv.Key + "name"))
{
dataTable.Columns.Add(fv.Key + "name");
}
row[fv.Key + "name"] = fv.Value;
}
dataTable.Rows.Add(row);
}
///////
private object getAttributeValue(object entityValue)
{
object output = "";
switch (entityValue.ToString())
{
case "Microsoft.Xrm.Sdk.EntityReference":
output = ((EntityReference)entityValue).Name;
break;
case "Microsoft.Xrm.Sdk.OptionSetValue":
output = ((OptionSetValue)entityValue).Value.ToString();
break;
case "Microsoft.Xrm.Sdk.Money":
output = ((Money)entityValue).Value.ToString();
break;
case "Microsoft.Xrm.Sdk.AliasedValue":
output = getAttributeValue(((Microsoft.Xrm.Sdk.AliasedValue)entityValue).Value);
break;
default:
output = entityValue.ToString();
break;
}
return output;
}
}
Subscribe to:
Posts (Atom)

