tag:blogger.com,1999:blog-60703828052421472832024-02-03T01:47:19.901+11:00Matt's .Net Blog for Software DevelopersThe good, the bad and the ugly of software development on the .NET platform.Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.comBlogger52125tag:blogger.com,1999:blog-6070382805242147283.post-90564232698392386842020-04-05T19:14:00.002+10:002020-04-18T17:43:15.445+10:00Convert Non-AC3 Audio to AC3 for movies<span style="color: red;"><b>6/4/2020: </b></span><br />
<span style="color: red;"><b>1. Fixed a bug where it was only processing files where the channels were greater than 6, not when then codec name was not compatible</b></span><br />
<span style="color: red;"><b>2. Fixed a bug where special characters in filenames were causing issues.</b></span><br />
<h2>
<b>Plex issues on the XBOX One</b></h2>
The Plex on the XBOX One is a decent movie player, however there's been an issue for a while (years) where the XBOX won't play videos properly in the case when the video needs to be transcoded.<br />
<br />
The XBOX can play most common video formats, but it can't play some of the newer audio formats natively (TRUEHD, DOLBY ATMOS, DTX:X) and it's can't play regular old DTS either. In order to play these Plex transcodes and although it play fine on other devices, for some reason it causes massive buffering on the XBOX one when transcoding is required.<br />
<br />
There doesn't seem to be a solution in sight, and the PLEX devs seemed to have given up, so I've written this script to to go through my existing video library and convert any audio streams to be natively compatible with the XBOX One.<br />
<br />
<br />
<h2>
<b>Preparing the Script</b></h2>
<div>
The script is a Power Shell script and to use it it's quite simple.</div>
<div>
<br /></div>
<div>
1. Download FFMpeg:</div>
<br />
<a href="https://ffmpeg.zeranoe.com/builds/">https://ffmpeg.zeranoe.com/builds/</a><br />
<br />
2. Unzip just the contents of the "bin" folder to a folder of your choice. You can unzip the other files but they are not needed.<br />
<br />
e.g. ffmpeg.exe and ffprobe.exe to a folder called "ConvertToAC3"<br />
<br />
3. Create a new file in the same folder as the FFMpeg files and paste the script into it. Make sure the extension of the file is ps1 (i.e. convert.ps1)<br />
<br />
4. Alter the script at the top changing the following variables<br />
<b><br /></b>
<b>$videos_path</b><br />
This needs to be set to the path of your video library<br />
<b><br /></b>
<b>$preview</b><br />
When 0, this will convert the files when the script is run. When the value is 1 it will only show you what it will attempt to convert and not actually do a conversion.<br />
<br />
5. Save the changes to the script<br />
<br />
6. Before we run the script, make sure you have double the space available on your hard drive - as it will duplicate each file.<br />
<h2>
<b><br /></b></h2>
<h2>
<b>Allowing Powershell Scripts To Run</b></h2>
<div>
You may need to run the following command in a power script session before getting started as Power Shell Scripts are disabled by default. You need run the session as an admin to allow this command to run to allow the script to execute.</div>
<div>
<pre style="border-radius: 3px; border: 0px; box-sizing: inherit; color: #242729; font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, sans-serif; font-size: 13px; font-stretch: inherit; font-variant-east-asian: inherit; font-variant-numeric: inherit; line-height: inherit; margin-bottom: 1em; margin-top: 0.5em; max-height: 600px; overflow-wrap: normal; overflow: auto; padding: 12px 8px; vertical-align: baseline; width: auto;"><code style="border: 0px; box-sizing: inherit; font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, sans-serif; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline; white-space: inherit;">set-executionpolicy remotesigned</code></pre>
</div>
NOTE: to run as admin, right click on the Windows Power Shell icon and click "Run As Administrator".<br />
<br />
<br />
<h2>
<b>What Does the Script do?</b> </h2>
1. The script goes and find every MKV file under the path that you have set (recursively).<br />
2. It them inspects each one (using FFProbe) to see if it needs to be converted<br />
3. If it decects that he MKV needs to be converted, it will use FFMPEG to convert audio (only the main stream) and creates a new a video file with the untouched video and the new audio as AC3.<br />
4. It will take a while depeding the on the number of videos you have.<br />
5. Once it's finished file it will rename the original the *.mkvold, so that Plex will ignore it and you can delete it once you are happy that the audio has been converted corrected<br />
<br />
<h2>
Thanks To...</h2>
I'd like to thank <a href="https://dailysysadmin.com/KB/Article/2055/use-ffmpeg-to-convert-a-dts-soundtrack-to-ac3-without-reencoding-video/" target="_blank">Robin Clarke</a> who's work with FFMPEG gave me a basis for my script.<br />
<br />
<h2>
Here's the Script</h2>
<div>
<br /></div>
<textarea cols="200" rows="100" style="height: 557px; margin: 0px; width: 945px;">## ---- SETTINGS ----- ##
## Change this directory to your video library ##
$videos_path = "Z:\Videos\TV"
## Change this value if you just want to preview what will be converted (0 = convert, 1 = preview)
$preview = 0
$fileextension = "mkv"
$filepostfix = "__AC3"
## ---- END SETTINGS ----- ##
$ErrorActionPreference = "Stop"
$ffprobe = ".\ffprobe.exe"
$ffpath = ".\ffmpeg.exe"
Write-Output "--------------------------------------------------------------------"
Write-Output "Starting"
Write-Output "--------------------------------------------------------------------"
#Include all files with the extension given, exclude all files already processed.
$sources = Get-ChildItem ($videos_path + "*") -Recurse -Include ("*." + $fileextension)
foreach ($source in $sources)
{
### Test File For Conversion ###
$ffprobeoutput = & $ffprobe -i $source -show_streams -select_streams a:0 -print_format json -loglevel quiet | Out-String
$streams = $ffprobeoutput | ConvertFrom-Json
$codec_name = $streams[0].streams.codec_name
$codec_channels = $streams[0].streams.channels
$codec_name_supported = $codec_name -eq 'ac3' -Or
$codec_name -eq 'aac' -Or
$codec_name -eq 'eac3' -Or
$codec_name -eq 'mp3'
$codec_channel_supported = [int]$codec_channels -le 6
$base_name = (Get-Item -LiteralPath $source).Basename
$dir_out = (Get-Item -LiteralPath $source).DirectoryName
Write-Output "--------------------------------------------------------------------"
Write-Output "Found: $base_name in folder $dir_out"
Write-Output "Codec Name Supported: $codec_name"
Write-Output "Codec Channels Supported: $codec_channels"
Write-Output "Codec Name Supported: $codec_name_supported"
Write-Output "Codec Channels Supported: $codec_channel_supported"
if (-Not ($codec_name_supported -And $codec_channel_supported))
{
#### Convert the file to AC3 ####
if (($preview -eq 0))
{
$tmp_output = Join-Path $dir_out -ChildPath ($base_name + $filepostfix + "." + $fileextension)
& $ffpath -i $source -map 0 -vcodec copy -scodec copy -acodec ac3 -b:a 640k $tmp_output -loglevel 8 -stats -y
$rename_to = ($base_name + "." + $fileextension + "old")
Rename-Item -LiteralPath $source -NewName $rename_to
Write-Output "Old File: $rename_to"
}
}
else
{
Write-Output "Already AC3 --> Skipping..."
}
}
Write-Output "--------------------------------------------------------------------"
Write-Output "Finished"
Write-Output "--------------------------------------------------------------------"
pause
</textarea>
<br />
<div>
<br /></div>
<br />Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-6897092204068718342014-05-20T20:25:00.000+10:002014-05-20T20:25:07.327+10:00MS CRM Simulating OnChange Javascript event for Owner fieldYou might get a surprise when you add the On Change event to an owner field - you will find that it doesn't fire. The reason for this is that the when changing the owner of a record the form is actually saved.<br />
<br />
Lucky for you, there is a way to achieve the same functionality as the On Change, and it's quite straight forward.<br />
<br />
<b>Save Mode = Assign</b><br />
<br />
Basically, you can add a On Save event (making sure you tick the pass the context box when you set it up), and then you can find out what triggered the On Save event inspecting the <a href="http://msdn.microsoft.com/en-us/library/bf64f471-7582-45e7-b8ed-370bfbecda68#BKMK_GetSaveMode" target="_blank">SaveMode </a>and finding out if it was triggered by an "Assign".<br />
<br />
<b>OwnerId Is Dirty Check</b><br />
<br />
The only other thing you'll need to do is check also if the OwnerId field is dirty. Why is this needed? Well, imagine that you've got a required field that you haven't filled in and then you go to change the owner. As changing the Owner causes an On Save event, this also triggers validation and you'll get an error message indicating the field is required before it gets to your logic, and the Save won't actually occur. This leaves the form in state where the OwnerId has actually been changed but your "OnChange" logic won't fire on the next save because the logic is looking for an Save Mode of "Assign" - which won't be raised again until someone attempts to re-assign the record. So, the next best thing is to do a check to see if the OwnerId is dirty (which will only happen if the Save attempt failed validation when the owner is changed) and your on change logic can fire then. Not perfect, but good enough in most situations.<br />
<br />
So here's an example of some code that will do the job:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>function Form_OnSave(context) {
var saveMode_Assign = 47;
if (context.getEventArgs().getSaveMode() == saveMode_Assign
|| Obs.getAttribute("ownerid").getIsDirty()) {
OwnerId_OnChange(); // Put your logic here!
}
}
</code></pre>
<br />
<br />
<br />
<br />Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-29360936982937093212014-03-22T10:42:00.002+11:002014-03-22T10:42:09.669+11:00S#arp Architecure, Multiple Database and Class MapsRecently, I've been updated a ASP.NET MVC project which is based on the S#arp Architecture. I needed to extend the existing function to talk to a second database.<br />
<br />
In order to do this, I followed the S#arp Architecture guide to multiple databases which can be found <a href="http://sharp-architecture.readthedocs.org/en/v3/nhibernate/multiple-dbs.html" target="_blank"><b>here</b></a>. This shows you how to add a second NHibernate configuration to your project and how to apply an attribute to your repository so it knows that which NHibernate configuration to use. This seemed to be perfect as out of the box, NHibernate didn't seem to have the facility to support multiple database sessions - well at least not easily.<br />
<br />
So following the guide, I integrated the changes into the existing software - unfortunately this didn't work.<br />
<br />
The issue was that I had the POCO classes and NHibernate Class Maps for both databases in the one assembly NHibernate would attempt to apply the class maps and POCO classes to only one database Session - causing NHibernate to throw "No Persister Found" errors and similar errors. As I didn't like the idea of splitting the project up into two assemblies for the sake of it (one for each database), I went looking for alternatives.<br />
<br />
To fix the POCO issue was easy, I just created an empty interface representing each database and applied that interface to each of the POCO objects accordingly, then in AutoPersistenceModelGenerator I simply had two different versions of the Generate method for each database which I used when settings up my Sessions in the calls to NHibernateSession.Init() and NHibernateSession.AddConfiguration().<br />
<br />
The Class maps were not so easy, after doing a lot of research and finding that ClassMaps can't be filtered out in the Same way as POCO objects, I was very much stuck until I found <a href="http://iamnotmyself.com/2011/06/22/using-fluent-nhibernate-classmaps-youre-doing-it-wrong/" target="_blank"><b>this blog post</b></a>. Although this blog post had nothing to do with multiple databases, it did point out that in the S#arp Architecture you should not use ClassMaps, but instead use Overrides that are provided by the S#arp Architecture.<br />
<br />
In the S#arp Architecture when using AutoMapping, Overrides represent those classes which shouldn't be AutoMapped on conventions and instead have custom mappings that don't match the convention. The Overrides are basically the same ClassMaps and also use the same syntax.<br />
<br />
To my surprise after converting the ClassMaps to Overrides (which only took a few minutes), it seems that S#arp Architecture could then work out which Override (i.e. Map) applied to which database and applied the where they were appropriate - no more errors. And because there were no long any Class Maps, NHibernate wasn't trying to automatically assume that all ClassMaps belonged to the current Session that was being initialized.<br />
<br />
Anyhow, thought I would document this in case someone else is facing the same situation.<br />
<br />
:)<br />
<br />
<br />
<br />
<br />Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-27177687447311305412013-02-02T12:11:00.000+11:002014-03-22T09:53:00.793+11:00Fiddler, CRM and Javascript Time Saver<span style="color: red;">UPDATE: Microsoft has changed the way they send the JavaScript to the browser (<a href="http://blogs.msdn.com/b/crminthefield/archive/2013/10/23/where-did-my-scripts-go-how-to-debug-form-javascript-in-crm-2011-ur15-and-higher.aspx" target="_blank">See here for more info</a>) so this tip will only work up until Rollup 11.</span><br />
<br />
To get some real productivity gains when working with Javascript and CRM you can introduce Fiddler into the mix. Fiddler has a feature called 'Auto Responder' which allows you intercept a file requested by a browser from a web server and replace it with a different file of your choice. <br />
<br />
By using this feature, it means that you don't have to go through the somewhat annoying process of modifying a Javascript file, deploying it and publishing it before you can test the changes; instead you can simply modify a Javascript file directly on your hard drive and then simply refresh the CRM form to test your changes. You only need to deploy and publish the change once you are happy with the modifications and want to commit them to CRM.<br />
<br />
Here's how to set it up:<br />
<br />
1. Install Fiddler on your local machine <br />
- you can download it from <a href="http://www.fiddler2.com/fiddler2/" target="_blank">here</a> for free:<br />
<br />
2. Choose an already published web resource file from CRM (e.g. new_js_accounts)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf7uQPKcWAld684svz49IaxMBfDNMVHoTwbTjZI7Kw56JC485M63A-lspbHUeiETe5h-CdHkreEy-mBFVL5RZtWo-bNrNZbgI5_0O3n6Akbx0loZIpTotWYIyXPwzwggBP04E1yt_r-L0/s1600/Capture1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf7uQPKcWAld684svz49IaxMBfDNMVHoTwbTjZI7Kw56JC485M63A-lspbHUeiETe5h-CdHkreEy-mBFVL5RZtWo-bNrNZbgI5_0O3n6Akbx0loZIpTotWYIyXPwzwggBP04E1yt_r-L0/s1600/Capture1.PNG" /></a></div>
<br />
<br />
3. Open up fiddler and click on the "Auto Responder" Tab<br />
<br />
4. Tick the "Enable Automatic Responses", then tick "Unmatches requests passthrough" (very important!)<br />
<br />
5. Click the add button and add a new rule, setting the first Rule box to the name of your web resource name (as highlighted on the URL in the first image above), and use the second box to "Find a file...", choosing the file which you want to replace the web resource with from your local machine. It should look something like this:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgieC03pRxMVp2GHVETdEUUO1aSqIZgxnwsLUu3CKr1ay2McU3rQ9ZoFJDfg4rUrL54LSGAGushP7moLnD0Ifdzb7E1PX2_fG5rAjC3kjQX4ofDreson3zU6R93Q1S6zWA7DtfArAoJZc4/s1600/Capture.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgieC03pRxMVp2GHVETdEUUO1aSqIZgxnwsLUu3CKr1ay2McU3rQ9ZoFJDfg4rUrL54LSGAGushP7moLnD0Ifdzb7E1PX2_fG5rAjC3kjQX4ofDreson3zU6R93Q1S6zWA7DtfArAoJZc4/s1600/Capture.PNG" /></a></div>
<br />
<br />
<br />
Once this is done, you should find that whenever you refresh your CRM Form now on it should point to the local copy of the file and ignore the server one. In Fiddler you can also turn this on/off by using the tick box next to the rule at any time.<br />
<br />
Don't forget to still upload and publish to CRM after you are done!<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com1tag:blogger.com,1999:blog-6070382805242147283.post-25108910700350083202012-09-15T11:05:00.003+10:002012-09-15T11:09:08.494+10:00Crm 2011 Developer Toolkit - Findings<span style="font-family: inherit;">I've started using the <a href="http://msdn.microsoft.com/en-us/library/hh372957.aspx" target="_blank">CRM 2011 Developer Toolkit</a> from the SDK, and I have to say I'm pretty happy with it in general - although it does have some small issues. </span><br />
<span style="font-family: inherit;"><br />
The Developer Toolkit takes care of a lot of common development tasks all within Visual Studio, including:</span><br />
<span style="font-family: inherit;">- Code Generation of Entities (similar to CrmSvcUtil.exe)</span><br />
<span style="font-family: inherit;">- Code Generation of Plugin templates</span><br />
<span style="font-family: inherit;">- Entity Registration with CRM</span><br />
<span style="font-family: inherit;">- Web Resource Management</span><br />
<span style="font-family: inherit;">- Deployment of Web Resources, Plugins, Workflows etc..</span><br />
<span style="font-family: inherit;"><br />
Here's some of the small issues I've found with the tool - none that I've found to be show stoppers.</span><br />
<span style="font-family: inherit;"><br /></span>
<b><i><span style="font-family: inherit;"><span style="background-color: white; color: #222222;">Error registering plugins and/or workflows. The resource string "ErrorSerializingRegFile" for the "RegisterPlugin" task cannot be found. Confirm that the resource name "ErrorSerializingRegFile" is correctly spelled, and the resource exists in the task's assembly. C:\Program Files (x86)\MSBuild\Microsoft\CRM\</span><wbr style="background-color: white; color: #222222;"></wbr><span style="background-color: white; color: #222222;">Microsoft.CrmDeveloperTools.</span><wbr style="background-color: white; color: #222222;"></wbr><span style="background-color: white; color: #222222;">CrmClient.targets 176 4 </span><wbr style="background-color: white; color: #222222;"></wbr><span style="background-color: white; color: #222222;">CrmPackage</span></span></i></b><br />
<span style="font-family: inherit;"><span style="background-color: white; color: #222222;"><br /></span>
<span style="background-color: white; color: #222222;">For whatever reason, the "</span><span style="background-color: white; color: #222222;"><b style="font-style: italic;">RegisterFile.crmregister" </b>file in the CrmPackage Project needs to be editable, so if you have it source controlled with TFS or it's read only for some reason - you'll get this message. Just simply check the file out or make the file writable.</span></span><br />
<span style="font-family: inherit;"><span style="background-color: white; color: #222222;"><br /></span>
<span style="background-color: white; color: #222222;"><i><b>The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))</b></i></span>
</span><br />
<span style="font-family: inherit;"><span style="background-color: white; color: #222222;"><br /></span>
<span style="background-color: white; color: #222222;">When generating the entities, you might get the above message. This is because of a bug which doesn't get on very well with Solution Folders in your solution. To work-around, simply remove the solution folders from you solution.</span></span><br />
<span style="font-family: inherit;"><span style="background-color: white; color: #222222;"><br /></span>
<span style="background-color: white; color: #222222;"><b>The Context Class is missing when you generate the entities</b></span></span><br />
<span style="font-family: inherit;"><br /></span>
<span style="color: #222222; font-family: inherit;">For some reason, the context class is not generated when you generate the LINQ entities as it is in CrmSvcUtil. Luckily, this is not required to use the LINQ functionality - but you'll need to make a few small changes</span><br />
<span style="color: #222222; font-family: inherit;">- Instead of creating a new context, you'll need to create a OrganizationServiceContext instead</span><br />
<span style="color: #222222; font-family: inherit;">- When you query against the OrganizationServiceContext, you'll need to use the <b>CreateQuery<<i>Entity</i>>()</b> syntax instead of the <b>CrmContext.<i>Entity</i>Set</b> syntax in your LINQ Queries</span><br />
<span style="font-family: inherit;"><span style="color: #222222;"><br /></span>
<span style="color: #222222;"><b>TIP: I'm just working on Javascript, and every time I deploy it deploys the plugins as well, how do I just deploy the Web Resources?</b></span></span><br />
<span style="font-family: inherit;"><span style="color: #222222;"><b><br /></b></span>
<span style="color: #222222;">If you want to deploy the web resources but avoid deploying any workflow and plugins (because of the amount of time to register these things), simply change the CrmPackage "<b><i>RegisterFile.crmregister"</i></b> file's Build Action from "Register Plugin" to "None". </span><span style="color: #222222;">The Build Action option can be found in the Visual Studio properties for the file. </span><span style="color: #222222;">This will stop workflows and plugins from registering and save a lot of time when your just working on web resources. If you want to enable registering of plugins & workflows again - simply do the reverse.</span></span><br />
<span style="color: #222222; font-family: Tahoma; font-size: x-small;"><br /></span>
<span style="color: #222222; font-family: Tahoma; font-size: x-small;"><br /></span>
<span style="color: #222222; font-family: Tahoma; font-size: x-small;"><br /></span>Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-1957754629596931782012-06-11T12:52:00.001+10:002013-07-20T10:20:42.803+10:00LINQ to CRM 2011 - Attribute Level Change Tracking Support<br />
I really like LINQ to CRM 2011, it makes development against CRM easier. However, out of the box LINQ to CRM has one big issue - it only supports entity level change tracking and not attribute level tracking.<br />
<br />
This means that whenever you retrieve something via LINQ, mark the entity as "changed" and submit the changes, whether or not you have changed any attributes - it will update ALL of the attributes for that entity in the database - because it does not distinguish which attributes have changed and which haven't before it makes the update.<br />
<br />
There are bad some side effects of this:<br />
- The new Auditing feature in CRM 2011 will record all the fields as changed, not just the ones you actually changed. This not only takes up database space, but makes it harder to read.<br />
- Plugins and Workflows will trigger unnecessarily if you have them filtered on particular fields which you did not actually change.<br />
- You may inadvertently overwrite changes that have occurred since you retrieved the record that you did not mean to.<br />
- If you have field level security on some of those attributes, they may cause an exception even though you never changed it's value.<br />
<br />
Luckily, LINQ to CRM allows you to intercept some useful events in it's life-cycle (Attach, SaveChanges, Execute) through the use of a sub-class or partial class, replacing it with your own functionality. Because of this, I've managed to write a class to overcome these issues and commit only the actual attribute changes to CRM rather than ALL of attributes.<br />
<br />
<b>So How does this class work?</b><br />
<br />
- Intercepts when an entity is "Attached" or "Detached" from the context. If the entity has a status of "unchanged" a shallow clone is taken of the entity and stored in an "originals" list; the clone is removed from the "originals" list if it is later detached. Note: Entities will have a status of "unchanged" if they have been retrieved via LINQ from CRM or manually attached using the Attach() method.<br />
<br />
- Intercepts the call to "SaveChanges()" method, inspects each entity that has been flagged as "changed" and compares it against the original version of the entity. If a change in attributes is detected then a new entity is created containing only the changed attributes and the entity's key; this new entity is put into a new "Deltas" collection. In addition, any entities that have not changed at all but are marked as "changed" are reset back to "unchanged", so they are not included in the commits that are made against CRM.<br />
<br />
- Intercepts when the Execute Method is called to commit the data. When the data is committed to the database, it uses the "UpdateRequest" class as you normally would using non-LINQ to CRM Entities. When this is done for entity in the list of entities marked as "changed", the Target property of the UpdateRequest object used is reset to use the delta version of the entity rather than the complete entity itself. This therefore avoids committing all attributes to the database, and instead only sends those fields that have changed.<br />
<br />
<b>Can I use it in a Plugin?</b><br />
<br />
Yes. You should even be able to use it in the online version of CRM.<br />
<br />
<b>How do I use it?</b><br />
<br />
- Place it in the same project as your LINQ to CRM context.<br />
- Change the namespace so that it macthes the same namespace as your LINQ to CRM Context.<br />
- Change the class name so that it macthes the same class name as your LINQ to CRM Context.<br />
- If you are doing read-only operations, to remove the change tracking overhead you can disable the functionality with the "<span style="white-space: pre-wrap;">EnableAttributeChangeTracking" flag - set it to false as soon as you new up your context.</span><br />
<br />
<b>Any Gotya's?</b><br />
<br />
- <b>Attach():</b> If you are going to new up an Entity for an update without retrieving the entity from CRM first, make sure you set the key and then Attach() the entity before you change the attributes on that entity. Otherwise it will not track the changes.<br />
<br />
- <span style="background-color: white; color: #202020; font-family: arial, sans-serif; font-size: 13px; line-height: 18px;"><b>MergeOption: </b>If you change the merge option, it may not work. <b>AppendOnly</b> (the default) is currently the only supported merge option.</span><br />
<br />
- <b>EntityCollection:</b> Currently, the EntityCollection attribute type is not supported when identifying changes between the original and the current version of an entity. EntityCollection attributes are always assumed to be "Changed" if included in an attribute, and will always be submitted for update.<br />
<br />
Cheers<br />
<br />
Matt
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
namespace Example.Linq
{
public partial class CrmContext
{
private List<Entity> _originalEntities = new List<Entity>();
private List<Entity> _deltaEntities = new List<Entity>();
private bool _enableAttributeChangeTracking = true;
/// <summary>
/// Enables or disables Attribute Level Change tracking.
/// Default is On.
/// </summary>
public bool EnableAttributeChangeTracking
{
get
{
return _enableAttributeChangeTracking;
}
set
{
if (_originalEntities.Count > 0 && value == false)
throw new Exception("You cannot disable attribute change tracking at this time, entities are already being tracked");
_enableAttributeChangeTracking = value;
}
}
/// <summary>
/// Overrides the base OnBeginEntityTracking method
/// When an entity is tracked, this adds the original unmodified version of that entity
/// to the unchangedEntities list (which contains the original version of every entity).
/// </summary>
/// <param name="entity"></param>
protected override void OnBeginEntityTracking(Entity entity)
{
base.OnBeginEntityTracking(entity);
if (_enableAttributeChangeTracking && entity.EntityState == EntityState.Unchanged)
{
var exists = _originalEntities.Where(x => x.Id == entity.Id).FirstOrDefault();
if (exists == null) _originalEntities.Add(ShallowClone(entity));
}
}
/// <summary>
/// Overrides the base OnEndEntityTracking method
/// For any entity that is detached from entity tracking, this also
/// removes it from the unchangedEntities list (which contains the original version of every entity)
/// </summary>
/// <param name="entity"></param>
protected override void OnEndEntityTracking(Entity entity)
{
base.OnEndEntityTracking(entity);
if (_enableAttributeChangeTracking)
{
var exists1 = _originalEntities.Where(x => x.Id == entity.Id).FirstOrDefault();
if (exists1 != null) _originalEntities.Remove(exists1);
var exists2 = _deltaEntities.Where(x => x.Id == entity.Id).FirstOrDefault();
if (exists2 != null) _deltaEntities.Remove(exists2);
}
}
/// <summary>
/// Overrides the base OnExecuting Method
/// For UpdateRequests, replaces the target entity on the update message
/// with an entity that only contains the key and the changed attributes.
/// </summary>
/// <param name="request"></param>
protected override void OnExecuting(OrganizationRequest request)
{
if (_enableAttributeChangeTracking && typeof(UpdateRequest) == request.GetType())
{
var updateRequest = (UpdateRequest)request;
var target = updateRequest.Target;
var newTarget = _deltaEntities.Where(x => x.Id == target.Id).FirstOrDefault();
updateRequest.Target = newTarget;
}
base.OnExecuting(request);
}
/// <summary>
/// Overrides the base OnSavingChangesMethod
/// For each entity, determines if an update is required.
/// If no update is required, it detaches and reattaches to set the entity back to a "unchanged" state.
/// Also create the entity that will ACTUALLY be subitted to the database (used in the OnExecuting method).
/// </summary>
/// <param name="options"></param>
protected override void OnSavingChanges(Microsoft.Xrm.Sdk.Client.SaveChangesOptions options)
{
// Clear the list of entities to be sumbitted
_deltaEntities.Clear();
// Mark any entities as unchanged that
// are only sending the key
var updated = this.GetAttachedEntities().Where(x => x.EntityState == EntityState.Changed).ToList();
foreach (var target in updated)
{
// Ignore updates where nothing has been updated
var unchanged = _originalEntities.Where(x => x.Id == target.Id).FirstOrDefault();
if (unchanged != null)
{
var cloneOfTarget = ShallowClone(target);
RemoveUnchangedFields(cloneOfTarget, unchanged);
_deltaEntities.Add(cloneOfTarget);
// Test to see if it's only the key left... if so ignore the update otherwise you will get a blank audit record
if (cloneOfTarget.Attributes.Count == 1 && cloneOfTarget.Attributes.First().Value != null)
{
if (cloneOfTarget.Attributes.First().Value is Guid || cloneOfTarget.Attributes.First().Value is Guid?)
{
if (cloneOfTarget.Id == (Guid?)cloneOfTarget.Attributes.First().Value)
{
this.Detach(target);
_deltaEntities.Remove(cloneOfTarget);
target.EntityState = EntityState.Unchanged;
this.Attach(target);
}
}
}
}
}
base.OnSavingChanges(options);
}
protected override void OnSaveChanges(SaveChangesResultCollection results)
{
_deltaEntities.Clear();
base.OnSaveChanges(results);
}
/// <summary>
/// Overrides the base OnExecute Method
/// For Update Requests, if an error was thrown ignores it if Target is null (i.e. no need to update)
/// </summary>
/// <param name="request"></param>
/// <param name="exception"></param>
protected override void OnExecute(OrganizationRequest request, Exception exception)
{
///
if (_enableAttributeChangeTracking && typeof(UpdateRequest) == request.GetType())
{
var updateRequest = (UpdateRequest)request;
if (updateRequest.Target != null)
{
base.OnExecute(request, exception);
}
}
else
{
base.OnExecute(request, exception);
}
}
/// <summary>
/// This method loops through comparing the changed entity with the unchanged entity
/// and removed the unchanged fields from the changed entity. Keys are kept.
/// </summary>
/// <param name="changed"></param>
/// <param name="unchanged"></param>
public void RemoveUnchangedFields(Entity changed, Entity unchanged)
{
// Lookp through the changed fields, if there is one missing from the unchanged list, add it as null so at least it will be compared.
foreach (var changedAttribute in changed.Attributes)
{
var key = changedAttribute.Key;
if (!unchanged.Contains(key))
unchanged.Attributes.Add(new KeyValuePair<string, object>(key, null));
}
// Loop through each attribute and compare properties
foreach (var unchangedAttribute in unchanged.Attributes)
{
var key = unchangedAttribute.Key;
var originalAttributeVal = unchanged.Attributes[key];
var changedAttributeVal = changed.Attributes[key];
// Fix any original value issues
if (originalAttributeVal != null)
{
// Fix any zero length strings
if (originalAttributeVal.GetType() == typeof(string) && string.IsNullOrWhiteSpace((string)originalAttributeVal))
{
originalAttributeVal = null;
}
else
{
// Fix any dates that aren't of a specified kind
if (originalAttributeVal.GetType() == typeof(DateTime) || originalAttributeVal.GetType() == typeof(DateTime?))
{
var date = (DateTime?)originalAttributeVal;
if (date.Value.Kind == DateTimeKind.Unspecified)
originalAttributeVal = date.Value.ToUniversalTime();
}
}
}
// Fix any changed value issues
if (changedAttributeVal != null)
{
// Fix any zero length strings
if (changedAttributeVal.GetType() == typeof(string) && string.IsNullOrWhiteSpace((string)changedAttributeVal))
{
changedAttributeVal = null;
}
else
{
// Fix any dates that aren't of a specified kind
if (changedAttributeVal.GetType() == typeof(DateTime) || changedAttributeVal.GetType() == typeof(DateTime?))
{
var date = (DateTime?)changedAttributeVal;
if (date.Value.Kind == DateTimeKind.Unspecified)
changedAttributeVal = date.Value.ToUniversalTime();
}
}
}
// Compare the values
if (originalAttributeVal == null && changedAttributeVal == null)
{
changed.Attributes.Remove(key);
}
else if (originalAttributeVal != null && changedAttributeVal == null)
{
// Do nothing, keep the attribute
}
else if (changedAttributeVal is EntityCollection)
{
// Do nothing, Leave it in... (complicated to compare).
}
else if (changedAttributeVal.Equals(originalAttributeVal))
{
if (unchangedAttribute.Value.GetType() == typeof(Guid) || unchangedAttribute.Value.GetType() == typeof(Guid?))
{
// Avoid removing the key
if (!((Guid?)unchangedAttribute.Value == unchanged.Id))
{
changed.Attributes.Remove(key);
}
}
else
{
changed.Attributes.Remove(key);
}
}
}
}
/// <summary>
/// Take a shallow copy of an entity
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
private static Entity ShallowClone(Entity entity)
{
var clone = new Entity(entity.LogicalName);
clone.EntityState = entity.EntityState;
clone.Id = entity.Id;
foreach (var attribute in entity.Attributes)
{
if (attribute.Value == null)
{
clone.Attributes.Add(attribute.Key, null);
}
else if (attribute.Value is EntityReference)
{
EntityReference entityReference = attribute.Value as EntityReference;
clone.Attributes.Add(attribute.Key, new EntityReference(entityReference.LogicalName, entityReference.Id));
}
else if (attribute.Value is Money)
{
Money money = attribute.Value as Money;
clone.Attributes.Add(attribute.Key, new Money(money.Value));
}
else if (attribute.Value is OptionSetValue)
{
OptionSetValue option = attribute.Value as OptionSetValue;
clone.Attributes.Add(attribute.Key, new OptionSetValue(option.Value));
}
else if (attribute.Value is EntityCollection)
{
// Don't copy EntityCollection values, just re-reference it.
EntityCollection entityCollection = attribute.Value as EntityCollection;
clone.Attributes.Add(attribute.Key, entityCollection);
}
else // value types
{
clone.Attributes.Add(attribute.Key, attribute.Value);
}
}
return clone;
}
}
}
</code></pre>
<br />
<span style="color: red;">15/09/2012 </span><span style="color: red;">Update: If you are using the entities generated by the <a href="http://msdn.microsoft.com/en-us/library/hh372957.aspx" target="_blank">CRM 2011 Developer Toolkit</a> rather than the <i><b>CrmSvcUtil.exe</b></i>, it doesn't generate a context class for some reason - so, in order to use the partial class above, you'll just need to create a simple class for the Context similar to the code below and use the CrmContext.CreateQuery<<i><b>Entity</b></i>>() syntax instead of the CrmContext.<i><b>Entity</b></i>Set when querying.</span><br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk.Client;
namespace Example.Linq
{
public partial class CrmContext : OrganizationServiceContext
{
/// <summary>
/// Constructor.
/// </summary>
public CrmContext(Microsoft.Xrm.Sdk.IOrganizationService service) :
base(service)
{
}
}
}
</code></pre>
<br />Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com28tag:blogger.com,1999:blog-6070382805242147283.post-1842015952427279832012-04-15T15:43:00.015+10:002012-04-15T16:40:26.881+10:00CRM 4 or CRM 2011 - Change the recipient email addressBoth CRM 4 and CRM 2011 always chooses to use the primary email address (emailaddress1) when sending an email to an entity (such as Account or Contact), even though on receiving emails, CRM will map the email to the entity based on any of it's email addresses.<br /><br />Every now and then I've come across a customer request where they would like to choose which of an entities email address's are used to send an email to the entity (e.g. contact).<br /><br />So after a lot of experimentation, I've found a reliable way of achieving this. The key is to use a plugin on the pre-event step of the "Send" message (Send Email Message) of the email. I've found that changing the email address to be used before this is unreliable as CRM often overrides the change and re-maps it back to the primary email address.<br /><br />So, to summarise my example code below:<div><br />1) I've added an "new_emailpreference" optionset to the contact entity, which lets you choose which email address is used (emailaddress1, emailaddress2 or emailaddress3).<div><br /></div><div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGzlt3QtEZO0k6Brcrv84ebPRC2H-9OKAoIDCwuaokO_JNwcTH_czukjmA1y_cO0V8n2PdAx3AuhxrQbPq1UJjekKt0GOPu-99dhJ7kt2-bQuosxz3_zAstO9KHPuRh6zZiFQ9k9jDZ2I/s1600/ContactEmailAddress.PNG"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGzlt3QtEZO0k6Brcrv84ebPRC2H-9OKAoIDCwuaokO_JNwcTH_czukjmA1y_cO0V8n2PdAx3AuhxrQbPq1UJjekKt0GOPu-99dhJ7kt2-bQuosxz3_zAstO9KHPuRh6zZiFQ9k9jDZ2I/s400/ContactEmailAddress.PNG" border="0" alt="" id="BLOGGER_PHOTO_ID_5731508364622834930" style="cursor: pointer; width: 400px; height: 123px; " /></a> </div><div><br />2) I've attached the plugin as a pre-operation step of the email entity to the "Send" message.</div><div><br />3) The Plugin does the following:<br />- Grabs the email<br />- Loops through the "to" address (you could easily extend this to include cc and bcc as well), finding those activity parties which map to a contact.<br />- For each contact found, it retrieves the contact and determines if emailaddress2 or emailaddress3 has been chosen as the preferred email address to be used instead of the default.<br />- For that activity party, it then changes the "addressused" property to the chosen email address and updates the email record.<br /><br />4) <span style="font-size: 100%; ">When the email gets sent via Outlook or via the Email Router, the email address that is used in the "To" address is set correctly to the email address that was chosen.</span></div><div><br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRcvcgcMis7SErpgcR9UFxetY9ysd0RovkbqVyaze7de9ohkRygVZ08Q5gqkXCsDL2Bo1jqf1c9AL5bb4Z7ZFlJlRsiAupJzjRjAvsxcrKVrbn1J3FfgaqI_W0NE_mlEfaPAQ3r7BmzBc/s1600/ConfirmEmailAddress.PNG"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRcvcgcMis7SErpgcR9UFxetY9ysd0RovkbqVyaze7de9ohkRygVZ08Q5gqkXCsDL2Bo1jqf1c9AL5bb4Z7ZFlJlRsiAupJzjRjAvsxcrKVrbn1J3FfgaqI_W0NE_mlEfaPAQ3r7BmzBc/s400/ConfirmEmailAddress.PNG" border="0" alt="" id="BLOGGER_PHOTO_ID_5731508680021734450" style="cursor: pointer; width: 400px; height: 240px; " /></a><br /><br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoFUGkBtJKAyK2tMi-aFG-z3nGT78h34uS2WyX6vrAQ5xK4ig2k8_C1R-sNkpJEeZww7k7oUb2PQp4c3J1sBz-EhhP1GHt1gy-798XCvWs4X-nnZ9rVdLI3MN9_F-p23_mBsfit3mHR2E/s1600/ConfirmEmailAddressViaOutlook.PNG"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoFUGkBtJKAyK2tMi-aFG-z3nGT78h34uS2WyX6vrAQ5xK4ig2k8_C1R-sNkpJEeZww7k7oUb2PQp4c3J1sBz-EhhP1GHt1gy-798XCvWs4X-nnZ9rVdLI3MN9_F-p23_mBsfit3mHR2E/s400/ConfirmEmailAddressViaOutlook.PNG" border="0" alt="" id="BLOGGER_PHOTO_ID_5731508821931661650" style="cursor: pointer; width: 383px; height: 320px; " /></a> </div></div><div><br /></div><div><br /></div><div><br /></div><div>Here's the code....Note that it's for CRM 2011, however the code for CRM 4 is very similar and all the fields are the same..</div><div><br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>using System;<br />using System.Collections.Generic;<br />using System.Linq;<br />using System.Text;<br />using Microsoft.Xrm.Sdk;<br />using Microsoft.Xrm.Sdk.Query;<br /><br />namespace ExamplePlugin<br />{<br /> public class Plugin : IPlugin<br /> {<br /> public void Execute(IServiceProvider serviceProvider)<br /> {<br /> IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));<br /> IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));<br /> IOrganizationService service = factory.CreateOrganizationService(context.UserId); // null for SYSTEM user, otherwise User Guid<br /> <br /> ITracingService trace = (ITracingService)serviceProvider.GetService(typeof(ITracingService));<br /> Guid emailId = Guid.Empty;<br /><br /> if (context.PrimaryEntityName != "email")<br /> {<br /> return;<br /> }<br /><br /> if (context.InputParameters.Contains("EmailId") && context.InputParameters["EmailId"] is Guid)<br /> {<br /> emailId = (Guid)context.InputParameters["EmailId"];<br /> <br /> if (emailId == Guid.Empty)<br /> {<br /> return;<br /> }<br /><br /> try<br /> {<br /> // Retrieve the email<br /> var email = service.Retrieve("email", emailId, new ColumnSet(true));<br /> if(email.Contains("to"))<br /> {<br /> EntityCollection activityParties = (EntityCollection)email["to"];<br /><br /> foreach (var activtyParty in activityParties.Entities)<br /> {<br /> if (activtyParty.Contains("partyid") && ((EntityReference)activtyParty["partyid"]).LogicalName == "contact")<br /> {<br /> // Retrieve the contact<br /> var contact = service.Retrieve("contact", <br /> ((EntityReference)activtyParty["partyid"]).Id, <br /> new ColumnSet(<br /> new string[] <br /> { "emailaddress1", <br /> "emailaddress2", <br /> "emailaddress3", <br /> "new_emailpreference" }));<br /><br /> if (contact.Contains("new_emailpreference") && contact["new_emailpreference"] != null)<br /> {<br /> if (((OptionSetValue)contact["new_emailpreference"]).Value == 100000002)<br /> {<br /> if (contact.Contains("emailaddress2") && ((string)contact["emailaddress2"]) != null)<br /> {<br /> activtyParty["addressused"] = (string)contact["emailaddress2"];<br /> }<br /> }<br /> else if (((OptionSetValue)contact["new_emailpreference"]).Value == 100000003)<br /> {<br /> if (contact.Contains("emailaddress3") && ((string)contact["emailaddress3"]) != null)<br /> {<br /> activtyParty["addressused"] = (string)contact["emailaddress3"];<br /> }<br /> }<br /> }<br /> }<br /> }<br /><br /> service.Update(email);<br /> }<br /> }<br /> catch (Exception ex)<br /> {<br /> trace.Trace("Plugin Exception Encountered");<br /> throw new InvalidPluginExecutionException(String.Format("An error occured Excuting the Plugin {0}{1}{2}", ex.Message, Environment.NewLine, ex.StackTrace));<br /> }<br /> finally<br /> {<br /> trace.Trace("Plugin Finished Executing");<br /> }<br /> }<br /> else<br /> {<br /> return;<br /> }<br /><br /> <br /> }<br /> }<br />}<br /><br /></code></pre><br /></div><div><br /></div>Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-11319639079631666522011-08-14T18:59:00.006+10:002011-08-14T19:20:35.523+10:00How to break linked reports in CRM 2011<em><blockquote>Crm Exception: Message: Linked reports cannot be downloaded, ErrorCode: -2147220970</blockquote></em>
<br />
<br />If you are finding that linked reports are failing to display in CRM 2011, and you get the old "Unexpected Error" message and when you look at CRM's tracing you find the message above, check that you have not removed the "Report Type" field from any of the Reports views.
<br />
<br />It seems that the CRM team are using this column to launch linked reports and without it when a report is launched will go to link to download.aspx instead of the specified page.
<br />
<br />Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com2tag:blogger.com,1999:blog-6070382805242147283.post-32272317267752433492011-07-30T18:23:00.004+10:002011-07-30T18:48:06.784+10:00CRM 2011, Silverlight and Refresh IssueWhen I added a Silverlight 4.0 application to a CRM form, I discovered a problem when you referesh the page in the browser. The issue is that on refresh the Silverlight application is ready before the Xrm.Page.ui is loaded and ready to access from Silverlight.<br /><br />This was a major problem for me as I needed to know what CRM Form Type was, which is done by invoking the "getFormType" method on the Xrm.Page.iu property of the form.<br /><br />After trying a number of things, I found the only way to reliably access the form type was to attempt to keep accessing form type until a value was succesfully returned.<br /><br />So, for those interested, here's a snipped from my silverlight application which might help you:<br /><br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code> // To get around problem that form may not yet be full loaded<br /> // and so javascript can't be invoked yet, keep trying until<br /> // we can get the form type, waiting a second between each try.<br /> DispatcherTimer timer = new DispatcherTimer();<br /> timer.Interval = new TimeSpan(0, 0, 1);<br /> timer.Tick += (object s, EventArgs args) =><br /> {<br /> int? formType = null;<br /><br /> // Attempt to get the Form Type<br /> try { formType = CrmUtility.GetFormType(); }<br /> catch { }<br /><br /> if (formType != null)<br /> {<br /> timer.Stop();<br /><br /> if (formType == 2) // 2 = Update<br /> {<br /> // ... Do work here ...<br /> } <br /> }<br /> };<br /> timer.Start();<br /></code></pre><br /><br />Note that all GetFormType() method does is find the Xrm.Page.ui property and then calls .Invoke("getFormType") to get the current Form Mode from the regular Crm Javascript methods.<br /><br />Also, you would expect this code to go somewhere in the initialization of your Silverlight Application.Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com5tag:blogger.com,1999:blog-6070382805242147283.post-5375094086028341612011-06-05T21:00:00.005+10:002011-06-05T21:05:20.061+10:00CRM 2011 : The given plugin assembly source type is not supported for isolated plugin assemblies.<span style="font-weight:bold;"><div><span style="font-weight:bold;"><br /></span></div><div><span style="font-weight:bold;"><br /></span></div>The given plugin assembly source type is not supported for isolated plugin assemblies.</span><br /><br />If you are getting this error message, it's because you are trying to register an assembly as sand boxed but have it's location set to disk or GAC. This is not an allowed combination, instead if you need it to be sand boxed set the storage location to the database.<div><br /></div><div><br /></div><div><br /></div><div><br /></div>Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com3tag:blogger.com,1999:blog-6070382805242147283.post-24572547509646137552011-04-09T11:02:00.003+10:002011-04-09T11:22:40.243+10:00Running CRM 4.0 App on CRM 2011 ServerI came accross an issue running software on a CRM 2011 server that was designed for CRM 4.0. There error that was reported is as follows:<br /><br /><blockquote>Could not load file or assembly 'Microsoft.Crm.Sdk, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)</blockquote><br /><br />I was quite surprised to get this after being told by the CRM 2011 SDK that software targeting CRM 4.0 should still be compatible.<br /><br />After lookin at <a href="https://community.dynamics.com/product/crm/crmtechnical/b/crmdavidjennaway/archive/2011/02/16/using-crm-4-0-assemblies-on-a-crm-2011-server.aspx">this link</a> it seems that the CRM 2011 installation is forwarding any reference to the CRM 4.0 assembly to the CRM 2011 assembly via a publisher policy - which aren't 100% compatible.<br /><br />Although you can get around it by modifying your applications config file (which is described in the link above), this issue can also be fixed now by applying <a href="http://support.microsoft.com/kb/2466084">Rollup Pack 1</a> as indicated in the notes for the Rollup Pack:<br /><br /><blockquote>The Microsoft.Crm.Sdk.dll file has an incorrect publisher policy registered in the global assembly cache (GAC). Therefore, when you use an application that is built with this dependency, the application fails in Microsoft Dynamics CRM 2011 Server.</blockquote><br /><br />Good to know if you intend to run software that was designed for CRM 4.0 on a CRM 2011 server.Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-33172231427365529062011-02-28T21:35:00.014+11:002011-03-10T23:00:06.097+11:00Windows 2008 Server Domain Controller - Change Password SettingsHaving built a lot of Virtual Development Environments with Windows server 2008 and CRM, one thing that I always have to lookup is how to disable the annoying password settings so that the password doesn't have to be reset every 42 days. <br /><br />Setting this is not so straight forward, so below are the instructions on how to do this successfully on a Windows 2008 Server that is setup as a domain controller.<br /><br />1. Start --> All Programs --> Group Policy Management<br /><br />2. Expand Forest --> Domains --> (your domain) <br /><br />3. Right click on Default Domain Policy and Select "edit..."<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNUsDAPacqvMK9MVKUZ7r2W4nQUafzvcVyYT6zuknIMJ2YnQBo2hkOs9-hnHsDEYhPDk3ZY0X8bQCP3Ec-2-LWQ8D7noo4cVngafnyyjNBnr2iYdhOGOBjVVo0h-MpnDmpy5o-rXbcj5o/s1600/Capture1.PNG"><img style="cursor:pointer; cursor:hand;width: 376px; height: 316px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNUsDAPacqvMK9MVKUZ7r2W4nQUafzvcVyYT6zuknIMJ2YnQBo2hkOs9-hnHsDEYhPDk3ZY0X8bQCP3Ec-2-LWQ8D7noo4cVngafnyyjNBnr2iYdhOGOBjVVo0h-MpnDmpy5o-rXbcj5o/s400/Capture1.PNG" border="0" alt=""id="BLOGGER_PHOTO_ID_5578694237862271890" /></a><br /><br />4. Expand Policies --> Windows Settings --> Security Settings --> Account Policies<br /><br />5. Inside Password Polcies you will find all the relavent password settings.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCxEEe1HzyM6814QY0_KKyxx5XY70z9ul2WlExDjv2oeKFzo409KyO932KMjBO0Fph0J_FHf22OYde632eO8RB2WQdwYPFKV1OcjD28OZ5iLqvchyS2qbvJpTqNopwvEXN_ZBhcgN5P3Q/s1600/Capture2.PNG"><img style="cursor:pointer; cursor:hand;width: 400px; height: 164px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCxEEe1HzyM6814QY0_KKyxx5XY70z9ul2WlExDjv2oeKFzo409KyO932KMjBO0Fph0J_FHf22OYde632eO8RB2WQdwYPFKV1OcjD28OZ5iLqvchyS2qbvJpTqNopwvEXN_ZBhcgN5P3Q/s400/Capture2.PNG" border="0" alt=""id="BLOGGER_PHOTO_ID_5578694241792237042" /></a><br /><br />6. Setting Maximum password age to 0 will disable password expiry.<br /><br />7. Exit out and in the command line run "gpupdate.exe"<br /><br />Now you should be good to go :)Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com1tag:blogger.com,1999:blog-6070382805242147283.post-24379039011737328232011-02-14T14:59:00.004+11:002011-02-14T15:16:11.853+11:00CRM 4.0 SiteMap.xml and "Object doesn't support this property or method" error.Today I came across an unusual error where a custom SiteMap SubArea (mapping to a URL) entry was causing an "Object doesn't support this property or method" when the user was navigating to the other subareas in the sitemap configuration in the same page.<br /><br />Eventually I found out the SubArea's parent Group had the same ID, and this was causing the issue. Simply changing the Id's fixed the error. (Would have been nice to have an error that was raised somewhere which indicated this, but such is life.)<br /><br />I hope this saves someone some agony out there!Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com1tag:blogger.com,1999:blog-6070382805242147283.post-86518992532754398862011-01-17T14:55:00.004+11:002011-01-17T15:12:45.173+11:00How to check where your CRM 4.0 Plugin is being called fromRecently, I needed to figure out if a CRM 4.0 Plugin was being called from CRM or whether it was being called from the CRM API.<br /><br />Luckily, the context provides this information through the context.CallerOrigin property. You can use this to determine whether the plugin is being called by comparing the type of the property to the following types:<br /><br /><span style="font-weight:bold;"><span style="font-style:italic;">if(context.CallerOrigin Is ApplicationOrigin) // It's being called from the CRM Web Application<br /><br />if(context.CallerOrigin Is AsyncServiceOrigin) // It's being called from the Async Process<br /><br />If(context.CallerOrigin Is WebServiceApiOrigin) // It's being called from external to CRM Via Web Services.<blockquote></blockquote></span></span><br /><br />So there you have it...Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-24341799888370466782010-09-18T08:58:00.004+10:002010-09-18T09:12:24.649+10:00Enumerable Select Magic in C#<span xmlns=''><p>I'd always wanted to toy with the "Select" extension method that lives in the <strong>System.Linq</strong> namespace, and today I found a great opportunity to use it.<br /><br />I had to calculate the number of working days between two dates (e.g. no weekends), and to do this I used a mix of the Range method on the Enumerable class and the select method, plus a list of the weekdays. The result is below.<br /><br /></p><p><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>public int WorkingDays(DateTime startDate, DateTime endDate)<br />{<br /> int totalDays = endDate.Subtract(startDate).Days + 1;<br /> <br /> List<DayOfWeek> businessDays = new List<DayOfWeek>() <br /> { System.DayOfWeek.Monday, System.DayOfWeek.Tuesday, System.DayOfWeek.Wednesday, <br /> System.DayOfWeek.Thursday, System.DayOfWeek.Friday };<br /><br /> return Enumerable.Range(0, totalDays)<br /> .Select( d => startDate.AddDays(d))<br /> .Where( wd => businessDays.Contains(wd.DayOfWeek)).Count();<br /><br />}<br /></code></pre><br /> </p><p>Anyway, I think it's a great basic example of how to use the select method, so I thought I'd share it.</p></span>Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-7586883019821042462010-06-19T12:11:00.006+10:002010-06-19T12:53:49.912+10:00Visual Studio 2010, Web.Config Transformations and the Broken Bits<div>For those of you who are deploying web applications using the new Visual Studio 2010 features in the RTM version, keep these issues in mind when you are using the new web.config transformation features:</div><div><br /></div><div><br /></div><div><b><span class="Apple-style-span" style="font-size:large;">xdt:transform="Replace" attributes don't work properly</span></b><br /></div><div><br /></div><div>If you are are using application settings with a .net Project, you'll find that the XML will actually stored the value in an element rather than as a attribute. When you try to apply the "Replace" tranform on this, you'll find that the result after publishing changes the value from this</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre"> </span><value>mystringvalue</value><br /></div><div><br /></div><div>to this</div><div><span class="Apple-style-span" style="white-space: pre;"><br /><span class="Apple-tab-span" style="white-space:pre"> </span><value>mystringvalue<br /><span class="Apple-tab-span" style="white-space:pre"> </span></value><br /></span></div><div><value><div><br /></div><div>Unfortunately, this actually adds the spaces to the end of your value, and the only way round it at the moment is to actually modify your code with a string.Trim() function to ignore the additional spaces. This probably also occurs anywhere you have an element representing a value rather than an attribute representing a value in your web.config.</div><div><br /></div><div>For more information, see <a href="http://connect.microsoft.com/VisualStudio/feedback/details/544183/web-config-transform-writes-extra-line-break-spaces-to-values-elements-under-applicationsettings-section">here</a> and <a href="http://connect.microsoft.com/VisualStudio/feedback/details/568073/web-config-transform-adds-tabs-and-carriage-returns">here</a> for more information on the issue.</div><div><b><br /></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><b><span class="Apple-style-span" style="font-size:large;">xdt:transform="XSLT" does not exist and is not valid although documented!</span></b></span></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><b><br /></b></span></b></div><div>When you have a look at the documentation around the web.config transform, you'll find that there's this excellent feature where you can have a standard XSLT transform. Well, it looks like it was accidentally omitted from the final release of Visual Studio 2010, because when you go to use it, the syntax is not recognized at all, in fact it gives you this message:</div><div><br /></div><div>"Could not resolve 'XSLT' as a type of Transform".</div><div><br /></div><div>You can find more information about the issue <a href="http://connect.microsoft.com/VisualStudio/feedback/details/567969/in-a-config-transform-the-attribute-xdt-transform-xslt-is-not-recognised">here</a>.</div><div><br /></div><div>Cheers</div><div><br /></div><div>Matt.</div></value></div>Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-60173371660194181022010-06-12T22:24:00.005+10:002010-06-12T22:39:23.685+10:00CRM 4.0 QUERY T4 TemplateHi Everyone,<br /><div><br /></div><div>I've finally got around to publishing some work I've done recently which leverages LINQ to SQL, T4 Templates and the CRM 4.0 API to create a data access layer to CRM.</div><div><div><br /></div><div>Originally I started write a Query provider for LINQ, but found it to be an extreme amount of work - and decided instead to return to old faithful - LINQ to SQL - which I have a good grounding in.</div></div><div><br /></div><div>Recently, Microsoft have added a LINQ to CRM query provider to the SDK (Version 4.0.12) so you should check that out. If it doesn't suit you, here's an alternative.</div><div><br /></div><div><div><b>Features</b></div><div><ul><li>Uses LINQ to SQL for Querying, meaning that you have the complete power of LINQ to SQL at your disposal</li><li>Database Queries do not go through CRM API, so they are potentially faster</li><li>Uses the CRM 4.0 API for CUD (Create, Update, Delete) operations - so data integrity is maintained.</li><li>Can generate only selected entities, rather than the entire CRM Entity list.</li><li>Works with WCF RIA services and any other technology that takes advantage of LINQ to SQL</li><li>You don't need to Leave the Visual Studio Environment, T4 generation is done within Visual Studio.</li></ul><div><br /></div></div></div><div>Anyway, if you have the time or the need, check it out <a href="http://crmquery.codeplex.com/">here</a>.</div><div><br /></div><div>Cheers</div><div><br /></div><div>Matt</div><div><br /></div><div><br /></div><div><br /></div>Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-29640197298669624232010-05-07T09:32:00.002+10:002010-05-07T09:36:43.661+10:00CRM 4.0.12 SDK ReleaseMicrosoft have just released the Microsoft CRM 4.0.12 SDK, which has a number of new developer features.<br /><br />I'm still going through them, but I'm liking what I see.<br /><br />One that stood out to me is that they now have a CRM 4.0 LINQ provider included! Yay!<br /><br />To download the SDK and check it out, find it here:<br /><br /><a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=82E632A7-FAF9-41E0-8EC1-A2662AAE9DFB&displaylang=en">http://www.microsoft.com/downloads/details.aspx?FamilyID=82E632A7-FAF9-41E0-8EC1-A2662AAE9DFB&displaylang=en</a><br /><br />Cheers<br /><br />MattMatthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-29132337745925500312010-04-19T16:11:00.002+10:002010-04-19T16:16:26.433+10:00LINQ to SQL Entity Base Version 1.2 FinalHi everyone,<br /><br />I've published the latest version of the Entity Base (Version 1.2) on Codeplex.<br /><br />There were no source code changes since Version 1.2 Beta, although if you were previously on 1.1 there are a few bug fixes that you may be interested in.<br /><br />You can find the release here:<br /><br /><a href="http://linq2sqleb.codeplex.com/releases/view/43763">http://linq2sqleb.codeplex.com/releases/view/43763</a><br /><br />Cheers<br /><br />Matt.Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-21704405842159166192010-03-13T12:41:00.005+11:002010-03-13T13:05:28.837+11:00Windows 7 HomeGroup Issue - possible solutionI had been trying to setup my network at home with a home group, so that all documents, printers, movies, etc... where available easily from any PC in the house.<br /><br />Although I could join the homegroup with no issue, none of the computers could see the shares on the other, nor the default profile folders (documents, pictures etc..) and when I tried to view the full network map (found under the "Network and Sharing Center"), it was showing a strange network topology that didn't look anything like what I had, and didn't show the other machines properly.<br /><br />Everything was configured correctly - but it just didn't work. How Frustrating.<br /><br />After fiddling a good deal and looking up a few solutions in various forums, I almost gave up when I though i might check the configuration of the computer name, and found the setting below in the screenshot which I don't recall seeing before, but there it was under the System Settings -->Change Settings-->Network ID.<br /><br />Changing the value from "This computer is part of a busines network etc..." to "This is a home computer; it's not part etc.." fixed the issue.<br /><br />The Network map was now correct (infact I was impressed, it was spot on - even with multiple routers!), and so I repeated these steps on each computer in the network which also fixed their maps as well.<br /><br />I then removed all computers from the home group, create a new home group and joined all the PC's again to it. Once I did this, the home group was up and running. I have no idea why this works, and ever after re-booting it's back to the "business network" option, but all I can say is it solved my issue and now I can enjoy the fruits of Windows 7.<br /><br /><br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyNfK_Ubn5gKdGdntXvZWm5aSL8QcKZw2BtQOdb1C33ZEud4c0vtKuZD6WMHxuNTcCKJgEj8hre74ZsHWxt0pILDT0aAGe3w-nWtLAaA5MbKFGep7IIVDn4_EutK9fAk6j4n7-bErQWj4/s1600-h/Capture.JPG"><img style="WIDTH: 400px; HEIGHT: 191px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5447929035320596610" border="0" alt="" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyNfK_Ubn5gKdGdntXvZWm5aSL8QcKZw2BtQOdb1C33ZEud4c0vtKuZD6WMHxuNTcCKJgEj8hre74ZsHWxt0pILDT0aAGe3w-nWtLAaA5MbKFGep7IIVDn4_EutK9fAk6j4n7-bErQWj4/s400/Capture.JPG" /></a>Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-80176822257542097912009-12-17T09:24:00.003+11:002009-12-17T12:37:03.277+11:00"Service Unavailable" message in CRM 4.0If you are running Windows 2003 server with a CRM Installation and you get the "Service Unavailable" message, check the application pool for CRM in IIS.<br /><br />If it's stopped, start it again, and try going to the CRM web page again. If the CRM application pool stops again, it's likely that you've just applied patch <a href="http://support.microsoft.com/kb/973917">KB973917</a>, try removing this patch from the server, reboot, this should fix the problem.<br /><br />Cheers<br /><br />MattMatthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com1tag:blogger.com,1999:blog-6070382805242147283.post-49000387324120624792009-12-03T07:24:00.003+11:002009-12-03T07:32:40.340+11:00LINQ to SQL Entity Base - Version 1.2 Beta<p>Hi everyone,<br /><br />I've released a beta of the LINQ 2 SQL Entity Base - included in it is the following changes since the V1.1 final:</p><p></p><ul><li><strong>Bug Fix:</strong> If the entity's state is "New", LINQ to SQL Tries to attach an FK references as "New" as well - even though this was unintended. Fixed it so it does not try and insert FK referenced entities when adding a new object. Thanks Mark & Stephan for finding/fixing the issue</li><li><strong>Bug Fix:</strong> If the onModifyKeepOriginal parameter on SetAsChangeTrackingRoot() is set to true and an object is detached from a collection (i.e. x.remove(y)), the object was not being attached properly to the data context when SynchroniseWithDataContext() was called, causing an error to be thrown. Thanks Yaki for finding the issue!</li><li><strong>Bug Fix:</strong> When EntityState == CanceNew, don't change to EntityState == Deleted when SetAsDeleteOnSubmit is called.</li><li><strong>Feature:</strong> Added ShallowCompare Method, which compares the properties of two entities and returns true if all the properties match (compare LINQ to SQL ColumnAttribute properties only).</li><li><strong>Feature:</strong> If the onModifyKeepOriginal parameter on SetAsChangeTrackingRoot() is set to true, and an object is modified (i.e. EntityState == Modified) and then modified again in a way which set's it back to be the same as property values as it's original state, the entity will now have it's EntityState set to back Original (instead of remaining as EntityState == "Modified"). </li></ul><p>It can be downloaded here:</p><p><a href="http://linq2sqleb.codeplex.com/">http://linq2sqleb.codeplex.com</a></p><p>Cheers</p><p>Matt.</p>Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-4264657047732644302009-10-27T18:29:00.006+11:002009-10-27T18:34:18.526+11:00Address Attribute Length Fun in CRM 4.0When you extend the length of Address attribute on a Contact or Account, this does not actually extend the length of the attribute in the database auto-magically only the length of the data that can be entered on the UI.<br /><br />The address details are not stored agains the customer, they are stored in a seperate entity called "Address".<br /><br />In order to extend the address you need to update the "Address" attribute too.<br /><br />See this KB for more info:<br /><br /><a href="http://support.microsoft.com/kb/973029">http://support.microsoft.com/kb/973029</a>Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com0tag:blogger.com,1999:blog-6070382805242147283.post-17966482521604809662009-10-27T17:41:00.007+11:002009-11-20T07:46:46.074+11:00Data Migration Manager Troubles with CRM 4.0Today I spent quite a lot of time trying to figure out why the data migration manager would not let me sign in.<br /><br />I'd just installed Rollup Pack 7, and noticed that there was no Data Migration Manager Update available in Rollup Pack 7, so I installed Rollup Pack 6's version of this update.<br /><br />Everytime I tried to login, it would complain that ...<br /><br />"You must have the System Administrator Security Role to use this Tool".<br /><br />I was puzzled, I checked multiple times to make sure that the user I put in during the install was correct, I searched the web - but this error had not be reported anywhere by anyone.<br /><br />Finally I decided to remove the Rollup Pack 6 update (only the update) for the Data Migration Manager, and to my surprise this worked! Infact I was even more surprised to find that it did not complain that there were updates available for it (which it usually does...).<br /><br />Anyway, I re-installed the Data Migration Manager Rollup 6 Update and it now works.<br /><br />So the fix is to Re-install if you get this error message.<br /><br /><span style="color:#ff0000;">[UPDATE] Came into work this morning, and a member of my team had exactly the same problem on a completely different server, Wonder if there's a Data Migration Manager Rollup-7 that hasn't been upload to the MS Download Site yet?</span><br /><span style="color:#ff0000;"></span><br /><span style="color:#ff0000;">[UPDATE2] There's a Rollup Pack 7 installer for the Data Migration Manager Now, however haven't tried it yet to see if it works personally, but apparently it does.</span>Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com5tag:blogger.com,1999:blog-6070382805242147283.post-10398618093241652702009-09-04T17:16:00.009+10:002011-08-14T19:24:01.863+10:00Default Organization For User is incorrect in CRM 4.0<p>Occasionally you come across the situation where the default organization has been changed and some of the users are still mapped with the default to the old database. This is quite annoying and can actually point your custom aspx pages to the wrong organization.
<br /></p><p>To fix this issue, I've written a SQL Server script which basically does the following:
<br /></p><ol><li>Finds the specified organization in the MSCRM_CONFIG database (the one that you want to be the default)
<br /></li><li>Finds the specified user ( which has the incorrect default database ) in the organization database which is intended to be the default (Based on '<domain>\<username>')
<br /></li><li>Finds the user details in the MSCRM_CONFIG database from the specified user found in the organization database
<br /></li><li>Updates the user record in the MSCRM_CONFIG database to have the specified default organization set.
<br /></li></ol><p>Notes on running the script:
<br /></p><ol><li>Backup your MSCRM_CONFIG database first! This is unsupported by Microsoft and Me!
<br /></li><li><div>Set the Domain\Username & Organization Name in the section titled 'PLEASE FILL IN THE FOLLOWING'
<br /></div><ol><li>The @UserDomainName is the same as found the DomainName box in the User CRM Screen
<br /></li><li>The @DefaultOrganization should be the name of the organization as shown in the deployment manager
<br /></li></ol></li><li><div>The script is designed to be executed in the context of the organization database which you want to be the default for the specified user, please make sure that this is set to the correct database before running
<br /></div><ol><li>i.e. you can do this by using the 'Use <databasename>' command in SQL first before executing the script. </li></ol></li><li><span style="color:#ff0000;">UPDATE</span>: You may need to restart IIS in order for the change to take place.
<br /></li></ol><p>
<br /></p>
<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>
<br />DECLARE @DefaultOrganization AS VARCHAR (100);
<br />
<br />DECLARE @DefaultOrganizationId AS VARCHAR (100);
<br />
<br />DECLARE @UserDomainName AS VARCHAR (100);
<br />
<br />DECLARE @CRMUserId AS VARCHAR (100);
<br />
<br />DECLARE @UserId AS VARCHAR (100);
<br />
<br />SET @UserDomainName = '<domain>\<username>';
<br />
<br />SET @DefaultOrganization = '<organizationname>';
<br />
<br />SELECT @DefaultOrganizationId = id
<br />FROM MSCRM_CONFIG..organization
<br />WHERE UniqueName = @DefaultOrganization;
<br />
<br />IF @DefaultOrganizationId IS NOT NULL
<br /> BEGIN
<br /> SELECT @CRMUserId = systemuserid
<br /> FROM systemuserbase
<br /> WHERE domainname = @UserDomainName;
<br /> IF @CRMUserId IS NOT NULL
<br /> BEGIN
<br /> SELECT @UserId = userid
<br /> FROM MSCRM_CONFIG..systemuserorganizations
<br /> WHERE crmuserid = @CRMUserId;
<br /> IF @UserId IS NOT NULL
<br /> BEGIN
<br /> SELECT 'BEFORE', *
<br /> FROM MSCRM_CONFIG..systemuser
<br /> WHERE id = @UserId;
<br /> UPDATE MSCRM_CONFIG..systemuser
<br /> SET defaultorganizationid = @DefaultOrganizationId
<br /> WHERE id = @UserId;
<br /> SELECT 'AFTER', *
<br /> FROM MSCRM_CONFIG..systemuser
<br /> WHERE id = @UserId;
<br /> END
<br /> ELSE
<br /> BEGIN
<br /> PRINT 'The User was Not Found in the Organization - Please check';
<br /> END
<br /> END
<br /> ELSE
<br /> BEGIN
<br /> PRINT 'The User was Not Found in CRM - Please check';
<br /> END
<br /> END
<br />ELSE
<br /> BEGIN
<br /> PRINT 'Default Organization Not Found - Please check';
<br /> END
<br />
<br /></code></pre>Matthew Hunterhttp://www.blogger.com/profile/15449365892205165530noreply@blogger.com4