Monday 28 January 2013

Fire your most irreplaceable team members now

Although the analysis may be a bit simplistic as there it makes no mention of skill level, I found this really interesting article here:
  1. An employee that voluntarily shares his knowledge with his coworkers, keeps nothing solely in his head, but rather strives to document publicly the knowledge and know-how he acquires on the job, is the most precious employee the company has. He knows that if he quits tomorrow, he is totally replaceable, in the sense that there's nothing he would need to teach his replacement - it's all there in the open. No knowledge is permanently lost. This is the guy you never actually want to replace. 
  2. An employee that hoards knowledge and positions himself as the omniscient guru, to whom all must come for answers, should be the first one fired. Sometimes people do this unintentionally. They don't see a problem with having all the answers. That's why managers need to instill into them the following mantra: the stuff you keep in your head should only be a copy.
I used to be think of myself as a number 1 type of worker and then I quickly morphed into to a number 2, why? Well, unfortunately management tend to value more a type 2 employee than a type 1, because they are not looking at the medium term, never mind the long term, they just care about the here and now and if somebody can pick up your work, then they can get rid of you. Short sighted? absolutely, but unfortunately this is the world we inhabit.

I'm trying to go back to my good ways of documenting everything and taking an attitude of I cannot afford NOT to document things. I'm not there yet, but I'm getting there. My advise is to try to work for a company where a type 1 worker will not get the sack when push comes to shove.

Wednesday 23 January 2013

Use metadata service in MS Dynamics CRM 2011 to list Entities' Display Names and logical names

I normally try to keep the entity's Display Name as close to the Logical Name as possible, but more often than not, even with the best of intentions, the names go out of sync. This can be a problem for many reasons, so I created a quick console app to write the Display Names and corresponding Logical Names to a file, for quick reference.

The code will only write to file the custom and customizable entities, ignoring system entities and intersect entities, those created by N:N relationships (oh, yes, behind the scenes it's similar to two 1:N relationships).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Xrm.Sdk;
using System.IO;

namespace XRM.Metadata
{
    public class Metadata
    {
      public List<EntityMetadata> RetrieveAllEntities(IOrganizationService service)
        {
            RetrieveAllEntitiesRequest request = new RetrieveAllEntitiesRequest()
            {
                EntityFilters = EntityFilters.Entity,
                RetrieveAsIfPublished = true
            };

            RetrieveAllEntitiesResponse resp = (RetrieveAllEntitiesResponse)service.Execute(request);

            return resp.EntityMetadata.OrderBy(x => x.LogicalName).ToList();          
        }

        public void WriteDetailsToFile(string fileName, List<EntityMetadata> entitiesMeta)
        {
            using (StreamWriter sw = new StreamWriter(fileName))
            {
                foreach (var entmeta in entitiesMeta)
                {
                    if (((bool)entmeta.IsCustomEntity || entmeta.IsCustomizable.Value) && !(bool)entmeta.IsIntersect)
                    {
                        if (entmeta.DisplayName.UserLocalizedLabel != null)
                        {
                            sw.WriteLine(string.Format("Display Name: {0} - Logical Name: {1}.", entmeta.DisplayName.UserLocalizedLabel.Label, entmeta.LogicalName));
                        }
                        else if (entmeta.Description.UserLocalizedLabel != null)
                        {
                            sw.WriteLine(string.Format("Display Name: {0} - Logical Name: {1} - Description: {2}", "Unknown", entmeta.LogicalName, entmeta.Description.UserLocalizedLabel.Label));

                        }

                        else
                        {
                            sw.WriteLine(string.Format("Display Name: {0} - Logical Name: {1} - Description: {2}", "Unknown", entmeta.LogicalName, "Unknown"));
                        }
                    }
                }
            }
        }
    }
}

Sunday 20 January 2013

Blogger Comment Spam Detection - part 2

I thought that this was pretty funny (I changed the title after I took the screenshot).


The only comment on a post about comment spam detection is, you guessed it, a spam comment. I'll leave it up so that it's there for posterity.

Thursday 17 January 2013

Blogger Comment Spam Detection

I noticed something strange going through the comments on my blog.

This comment, which was a reply to a comment, got directed to the spam comments:
Weell the information about the Joomla is very imformative because you tell all the important things which we can creat just watching your blog and it is also very easy to read and understand. I change many things just watching your blog. Thank you for the imformation you provide. 
Yet this comment was displayed:
Well the information about the Joomla is very imformative because you tell all the important things which we can creat just watching your blog and it is also very easy to latest news on information technology read and understand. I change many things just watching your blog.
They were both on the same post by the same blogger user.

The displayed comment had two fewer typos and was not a reply to a comment, other than that they are almost identical. I guess the spam filter was triggered for the former as it had too many red flags:
  1. One typo per line (average)
  2. A blog profile that's actually an html link to a page unrelated to the blog's theme.
  3. Reply to a comment?
Or was it simply the mention of Information technology on the latter comment, that got it through the spam filter?

Monday 14 January 2013

Search MS Dynamics CRM 2011 workflow definition (xaml) in PowerShell

Sometimes it's necessary to search the workflow definition quickly for an action, for instance: creating a task or creating a custom entity. If you have a few workflows in the system then you can probably get away with looking at them manually from the GUI, if you have many then it makes sense to search the xaml definition file.

The simplest option is described here:

  1. Export the solution containing your workflows.
  2. Extract the solution to a directory.
  3. Start PowerShell and navigate to the directory above.
  4. Search the definition with the following command, which will provide you with the filename:
 ls -Include *.xaml -Recurse | Select-String "YourSearchString" | Group-Object -Property filename | Format-List -Property name
Note that by default YourSearchString will be read as regular expression.

If you want to do negative matching you could try this:
 ls -Include *.xaml -Recurse | Select-String "YourSearchString" -NotMatch | Group-Object -Property filename | Format-List -Property name
This command can be very slow as the notmatches are going to be a lot more than the matches in the xaml, so use it at your peril.

Wednesday 9 January 2013

MS Dynamics CRM 2011 for Outlook issues looking at sub folders.

A few weeks one our customers was complaining that they were having issues with Outlook when accessing the Dynamics CRM sub folders. In essence, any view would display the error below and then Outlook and Dynamics would continue on their merry way, working fine.


The error traces weren't being terrible helpful:
Exception generated at: 18/12/2012 14:09:31Error Type: System.NullReferenceExceptionError Message: Object reference not set to an instance of an object.Error Stack Trace: at CommandBarOnDemandPopupWrapper..ctor(XmlNode controlXml, ExplorerWrapper explorer, PrepareMenuCallback callback)  ilOffset = 0x42at MenuState.PrepareMenu(CommandBarPopupWrapper parentMenu)  ilOffset = 0xF9at MenuState..ctor(IClientOrganizationContext clientOrgContext, String context, ExplorerWrapper explorer, Int32 index)  ilOffset = 0x1E2at <>c__DisplayClass2f.<SetupMenus>b__2a()  ilOffset = 0x3Cat <>c__DisplayClass31.<SetupMenus>b__2c(Object unused)  ilOffset = 0x0at <>c__DisplayClass1a.<QueueUserWorkItemSilent>b__15()  ilOffset = 0x0at ExceptionFilter.TryFilterAllCatch(Action body, Action`1 filter)  ilOffset = 0xF

Stack Frame: 
at OutlookUtility.HandleExceptionInternal(Exception exception, Boolean showMessageBox, IWin32Window messageBoxOwner, String errorMessage)  ilOffset = 0x107at <>c__DisplayClass17.<QueueUserWorkItemSilent>b__16(Exception ex)  ilOffset = 0x6at <>c__DisplayClassa.<TryFilterAllCatch>b__8(Exception ex)  ilOffset = 0xCat ExceptionFilter.TryFilterAllCatch(Action body, Action`1 filter)  ilOffset = 0x34at CommandBarOnDemandPopupWrapper..ctor(XmlNode controlXml, ExplorerWrapper explorer, PrepareMenuCallback callback)  ilOffset = 0x42at MenuState.PrepareMenu(CommandBarPopupWrapper parentMenu)  ilOffset = 0xF9at MenuState..ctor(IClientOrganizationContext clientOrgContext, String context, ExplorerWrapper explorer, Int32 index)  ilOffset = 0x1EAat <>c__DisplayClass2f.<SetupMenus>b__2a()  ilOffset = 0x65at <>c__DisplayClass31.<SetupMenus>b__2c(Object unused)  ilOffset = 0xBat <>c__DisplayClass1a.<QueueUserWorkItemSilent>b__15()  ilOffset = 0x16at ExceptionFilter.TryFilterAllCatch(Action body, Action`1 filter)  ilOffset = 0xFat <>c__DisplayClass17.<QueueUserWorkItemSilent>b__14(Object innerState)  ilOffset = 0x42at QueueUserWorkItemCallback.WaitCallback_Context(Object state)  ilOffset = 0x1Aat ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)  ilOffset = 0x8Eat QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()  ilOffset = 0x35at ThreadPoolWorkQueue.Dispatch()  ilOffset = 0x81at _ThreadPoolWaitCallback.PerformWaitCallback()  ilOffset = 0x51

The first thing we noticed was that there was a mismatch  in the update rollups installed, but then we found another workstation without the mismatch and the problem occurred.

We then applied a few patches to the workstation, including an upgrade from Outlook 2007 SP2 to SP3 but to no avail.

In a last ditch attempt to solve the issue, we upgraded to Outlook 2010 and it solved the issue. Not sure the customer will be happy with our proposed solution.

Friday 4 January 2013

What's the Powershell equivalent of back tick on bash?

I was wondering this myself today and it's one of them questions, where you say, well of course.

In Powershell, one uses $( ) to evaluate sub-expressions, so say that you wanted to pass your username to a PowerShell script.

You could do this:
$user = whoami
myscript.ps1 $user
But this is more elegant
myscript.ps1 $(whoami)
From here.

Thursday 3 January 2013

Find domain controller used for logon

I managed to lock out an account today so I was trying to login to the DC, but I couldn't get access to our SharePoint server which meant that I didn't have the environment information at hand, and because this is a new environment, I don't know the name of the servers off the top of my head, which meant that I needed a way to find out a domain controller.

It turns out that this is really easy. Logonserver is an environment variable that contains the domain controller that was used for logging on.

From PowerShell (again with elevated privileges):
Get-ChildItem ENV: | where {$_.Name -eq "logonserver"}
or
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().FindDomainController()  

From a Command Prompt (with elevated privileges):
set logonserver
or 
set L

If you want to get all domain controllers, not just the one you used to logon:
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers