When we first create new field names in crm we try to name them so they make sense to our users, but as time goes on a number of fields get classified by other names. This makes it hard to get the field names on forms without opening the form and finding it there. On larger forms this is a time consuming way to get the fieldname. Adding a ribbon button with some custom js fixes this. The js grabs all attributes on the form and replaces their label with the schemaname.
function SetAllLabelsToSchema() {
try {
var attributes = Xrm.Page.data.entity.attributes.get();
for (var ix = 0; ix < attributes.length; ix++) {
var attribute = attributes[ix];
var FieldName = attribute.getName();
if (Xrm.Page.getControl(FieldName) !== null) {
Xrm.Page.getControl(FieldName).setLabel(FieldName);
}
}
} catch (ex) {
//this is a non critical failure.
}
}
Another must have is a way to get the current records Id. In 2011 this was still easy as the Id was in the url. With the later updates you need to send a link in an email to steal the guid. The below js uses some DOM commands to copy the current record guid to the clipboard for quick use in development or queries.
function copyToClipboard(text) {
if (window.clipboardData && window.clipboardData.setData) {
// IE specific code path to prevent textarea being shown while dialog is visible.
return clipboardData.setData("Text", text);
} else if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
var textarea = document.createElement("textarea");
textarea.textContent = text;
textarea.style.position = "fixed"; // Prevent scrolling to bottom of page in MS Edge.
document.body.appendChild(textarea);
textarea.select();
try {
return document.execCommand("copy"); // Security exception may be thrown by some browsers.
} catch (ex) {
console.warn("Copy to clipboard failed.", ex);
return false;
} finally {
document.body.removeChild(textarea);
}
}
}
Wrightroll's ponderings
Friday, September 22, 2017
Monday, September 18, 2017
Dynamics CRM Audit Query
Due to a large number of records in our system we find that many times trying to open an audit log from a record results in a sql timeout error in CRM. Below I finally spent some time in coming up with a SQL query to get similar data. Replace the @objectid value with the record that you are attempting to view the audit for and you should get a returned table with the specified data.
NOTE: dates/times in the database are stored UTC, so some additional care should be taken with any dates returned in below query.
--drop table #audit
declare @objectid uniqueidentifier = '{AA2B1B39-02E7-E611-80C5-0050569E2CDA}'
select auditid, useridname, createdon, operation, AttributeMask, '~' + ChangeData + '~' ChangeData, objectid
into #audit
from audit a
where a.objectid = @objectid
declare @objecttypecode int = (select top 1 objecttypecode from audit where objectid = @objectid)
;with #tmp(auditid, useridname, createdon, operation, Attribute, AttributeMask, ChangeDataStr, ChangeData) as (
select auditid, useridname, createdon, operation,
LEFT(AttributeMask, CHARINDEX(',',AttributeMask+',')-1), STUFF(AttributeMask, 1, CHARINDEX(',',AttributeMask+','), ''),
LEFT(ChangeData, CHARINDEX('~',ChangeData+'~')-1), STUFF(ChangeData, 1, CHARINDEX('~',ChangeData+'~'), '')
from #audit
union all
select auditid, useridname, createdon, operation,
LEFT(AttributeMask, CHARINDEX(',',AttributeMask+',')-1), STUFF(AttributeMask, 1, CHARINDEX(',',AttributeMask+','), ''),
LEFT(ChangeData, CHARINDEX('~',ChangeData+'~')-1), STUFF(ChangeData, 1, CHARINDEX('~',ChangeData+'~'), '')
from #tmp
where ChangeData > ''
)
select auditid, useridname, createdon, operation, a.TableColumnName, ChangeDataStr
from #tmp t
join attribute a on t.attribute = a.ColumnNumber
join entity e on a.entityid = e.entityid and e.ObjectTypeCode = @objecttypecode
order by createdon desc
OPTION (maxrecursion 0)
-- normally recursion is limited to 100. If you know you have very long
-- strings, uncomment the option
NOTE: dates/times in the database are stored UTC, so some additional care should be taken with any dates returned in below query.
--drop table #audit
declare @objectid uniqueidentifier = '{AA2B1B39-02E7-E611-80C5-0050569E2CDA}'
select auditid, useridname, createdon, operation, AttributeMask, '~' + ChangeData + '~' ChangeData, objectid
into #audit
from audit a
where a.objectid = @objectid
declare @objecttypecode int = (select top 1 objecttypecode from audit where objectid = @objectid)
;with #tmp(auditid, useridname, createdon, operation, Attribute, AttributeMask, ChangeDataStr, ChangeData) as (
select auditid, useridname, createdon, operation,
LEFT(AttributeMask, CHARINDEX(',',AttributeMask+',')-1), STUFF(AttributeMask, 1, CHARINDEX(',',AttributeMask+','), ''),
LEFT(ChangeData, CHARINDEX('~',ChangeData+'~')-1), STUFF(ChangeData, 1, CHARINDEX('~',ChangeData+'~'), '')
from #audit
union all
select auditid, useridname, createdon, operation,
LEFT(AttributeMask, CHARINDEX(',',AttributeMask+',')-1), STUFF(AttributeMask, 1, CHARINDEX(',',AttributeMask+','), ''),
LEFT(ChangeData, CHARINDEX('~',ChangeData+'~')-1), STUFF(ChangeData, 1, CHARINDEX('~',ChangeData+'~'), '')
from #tmp
where ChangeData > ''
)
select auditid, useridname, createdon, operation, a.TableColumnName, ChangeDataStr
from #tmp t
join attribute a on t.attribute = a.ColumnNumber
join entity e on a.entityid = e.entityid and e.ObjectTypeCode = @objecttypecode
order by createdon desc
OPTION (maxrecursion 0)
-- normally recursion is limited to 100. If you know you have very long
-- strings, uncomment the option
Thursday, August 31, 2017
First look at CRM Editable Grids Post 1
Editable grids are this wonderful new feature in Dynamics 365, but for many of us longtime users with a lot of js security and business logic some additional work is needed before they are useful. I will be posting a few of the tricks I learn as I dive into incorporating them.
This first post will deal with limiting which fields a user can edit as many of the form security applied previously does not apply to Editable Grids. What do I mean by this? First off read only attributes applied to fields on a form do not honor this setting in editable grids. As such neither does calls to setDisabled in form js. Also as editable grids are set at an entity level, users are able to create their own views and add any column, even those not on any form, to those views and gain editable rights to them. Supposedly field level security is upheld on editable grids, but that does little to help if you don't want to go through the thousands of fields you have created and decide who has access to edit them.
The first step I took is to disable all fields in the selected row. I will not go into how to setup editable grids or wire up events as this is all documented nicely many places, but where I ran into trouble is what the js should look like to accomplish this goal.
var editableFields = ["emailaddress1", "telephone1"];
function Grid_And_Form(executionContext) {
var entityObject = executionContext.getFormContext().data.entity;
var attributes= entityObject.attributes.getAll();
for (var i = 0; i< attributes.length; i++) {
if (editableFields.indexOf(attributes[i].getName()) == -1) {
console.log(attributes[i].getName());
var attrControl= attributes[i].controls.get(0);
attrControl.setDisabled(true);
}
}
}
By passing executionContext to the js, and retrieving the entity we can get all attributes. From there getting the first control for each attribute (there should only ever be one, as you cannot add a field to a view multiple times) we are able to disable that field. Creating a list up front of fields we want to allow to be editable allows us to easily modify the list, and document which fields are available.
Thursday, May 24, 2012
double hop authentication resolution
The dreaded doublehop authentication issue was recently a thorn in my side no one was able to answer. Finally I have come to the answer. For anyone out there having this issue, there are only a few steps you need to follow to get you website to call your wcf with the logged on users authentication. I am not sure that all of the steps below are needed, but if it works I am willing to do them all. I have highlighted the key components of each that I believe together resolve this issue.
On the wcf method (not the interface declaration)
<OperationBehavior(impersonation:=ImpersonationOption.Allowed)>
Add a Behavior attribute to the WCF Project config (this can either be added directly to config or added via wcf configuration utility
<behaviors>
<serviceBehaviors>
<behavior name="customBehaviorName">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceAuthorization principalPermissionMode="UseWindowsGroups" impersonateCallerForAllOperations="true" />
</behavior>
</serviceBehaviors>
</behaviors>
In the website add the service reference to the WCF service
set up the serviceclient
Dim client As New ServiceReference1.Service1Client()
client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Delegation
client.ChannelFactory.Credentials.Windows.ClientCredential = System.Net.CredentialCache.DefaultNetworkCredentials
client.methodWithPassThroughAuthentication()
As a note, at the IIS level I also have Windows Authentication and ASP.NET Impersonatation enabled on the Website , and Anonymous Authentication and Windows Authentication enabled for the WCF service.
thanks to Peter T for providing the link that finally led me to figure this one out.
http://blogs.msdn.com/b/securitytools/archive/2009/11/04/double-hop-windows-authentication-with-iis-hosted-wcf-service.aspx
Bind enum to dropdownlist
Public Function GetListItemsFromEnum(enumType As Type) As ListItemCollection
'container to be returned
Dim items As New ListItemCollection()
'break down the enumerator items into key/value pairs
Dim names As String() = [Enum].GetNames(enumType)
Dim values As Array = [Enum].GetValues(enumType)
'piece together the key/pairs into the listitem collection
For i As Integer = 0 To names.Length - 1
items.Add(New ListItem(names(i).ToString, CInt(values(i)).ToString))
Next
'return it
Return items
End Function
Public Sub Page_Load(sender as object, e as eventargs) handles Me.Load
ddl.DataSource = GetListItemsFromEnum(GetType(EnumerationName))
ddl.DataTextField = "text"
ddl.DataValueField = "value"
ddl.DataBind()
End Sub
code thanks to http://forums.asp.net/t/1269514.aspx/1
Tuesday, March 27, 2012
Setting up Exchange OWA in Outlook
Having a test environment is a great thing. Having a test environment which is an exact virtual copy of production is a wonderful thing. However, as a developer connected to that test environment it is hard to keep up with meeting requests, emails, etc when outlook must be viewed through Exchange OWA.
After reaching my limit of sending emails and not having them go to their intended recipients (having been caught in the test environment exchange), and consulting our resident Network guru, here is the answer:
Add a host file record for the ip of the owa url.
for: https://12.123.12.123/exchange / https://webmail.domain.com/
add 12.123.12.123 webmail.domain.com
Next flushdns
Open cmd prompt and type ipconfig -flushdns
Then we begin adding the outlook account
Open control panel
Click Mail
Show Profiles
Add
Name the profile whaterver you want.
Ok
Manually configure server settings or additional server types
Next
Microsoft Exchange or compatible service
Next
More Settings
Click the Security tab
Check Always prompt for logon credentials
Click the Connection tab
check Connect to Microsoft Exchange using HTTP
then click on the Exchange Proxy Settings
type webmail.domain.com (this is the key. The url here needs to match the url on the owa certificate)
check the On fast networks, connect using HTTP first, then connect using TCP/IP
click ok
type the fqdn of the exchange server (i.e. server.domain.com)
type your email and check names.
If everything went successfully you should be able to finish as you would normally. Click next and if you want this to be the default profile be sure to set it on the mail profiles window.
Hope this helps someone else out there as I know I banged my head over this one for a while.
After reaching my limit of sending emails and not having them go to their intended recipients (having been caught in the test environment exchange), and consulting our resident Network guru, here is the answer:
Add a host file record for the ip of the owa url.
for: https://12.123.12.123/exchange / https://webmail.domain.com/
add 12.123.12.123 webmail.domain.com
Next flushdns
Open cmd prompt and type ipconfig -flushdns
Then we begin adding the outlook account
Open control panel
Click Mail
Show Profiles
Add
Name the profile whaterver you want.
Ok
Manually configure server settings or additional server types
Next
Microsoft Exchange or compatible service
Next
More Settings
Click the Security tab
Check Always prompt for logon credentials
Click the Connection tab
check Connect to Microsoft Exchange using HTTP
then click on the Exchange Proxy Settings
type webmail.domain.com (this is the key. The url here needs to match the url on the owa certificate)
check the On fast networks, connect using HTTP first, then connect using TCP/IP
click ok
type the fqdn of the exchange server (i.e. server.domain.com)
type your email and check names.
If everything went successfully you should be able to finish as you would normally. Click next and if you want this to be the default profile be sure to set it on the mail profiles window.
Hope this helps someone else out there as I know I banged my head over this one for a while.
Friday, March 16, 2012
Take that popup!
Ever been to a website that puts an annoying popup right over the link that you want to click. As if that isn't annoying enough some sites don't have any apparent way of closing said annoying popup. Introducing Internet Explorer developer tools. With a few quick steps you can blast that popup off the screen.
Subscribe to:
Posts (Atom)