How to remove a solution component that Dataverse refuses to let go of

Picture the scene. The Dataverse solution exports fine from DEV, sails through managed export, unpack and pack, and then face-plants the moment you try to import it on UAT with something like this:

Import of app module(s) failed: Invalid solution identified. There are more than one component with the same id added to the solution.
Component Name: AppSetting, Key:
parentappmoduleid:00000000-0000-0000-0000-000000000000
settingdefinitionid:7c1e5a92-6d38-4b0f-9a2e-1f8c4d6b93e0

More than one component with the same id. Great. Where did those come from? I certainly never added them. Off to the Solutions editor to find and remove the offenders — except they’re nowhere in the UI. Invisible. As far as the editor is concerned, they don’t exist, which is a bold position to take about something the import validator is very sure exists in triplicate.

After some digging, the culprit turned out to be the preferred solution setting. In my case the rogue components were a pair of AppSetting records with the same name, which Dataverse had silently created when I set the update recurrence interval on a Dynamics App. Because I’d marked one of my solutions as my preferred solution — a per-user setting — Power Platform quietly filed those new components into it. I never asked for them and never saw them added. Very considerate. Also the direct cause of the import blowing up.

It’s not a one-off, either. On a different project we configured an Azure Synapse Link for Dataverse on DEV, and later set it up by hand on UAT and Prod. That quietly seeded solution components too — a datalakefolder and a synapsedatabase — into the DEV solution. We only caught it because the solution gets exported and unpacked into source control, so the surprise components showed up in a diff before they could ruin a managed deployment to UAT. Lesson very much reinforced: unpack your solutions into version control.

The good news: there’s a perfectly civilised way to get these ghost components out that doesn’t involve unmanaged panic or rebuilding the whole solution. The bad news: it involves poking the Dataverse Web API by hand and pasting a fetch() into your browser console like it’s 2009 and we’re all still very excited about AJAX. Let’s go.

(A quick note: the URLs, GUIDs and solution names below are made-up examples. Substitute your own environment’s values. Also: this is a “know what you’re doing” operation — you’re removing something from a solution via the API, so make sure it’s the thing you actually mean to remove before you hit Enter.)

Step 1: Identify the solution

First we need two things about the solution: its GUID and its UniqueName.

  1. Go to make.powerapps.com, pick the correct environment — the one where the solution is still unmanaged, which is typically your DEV environment — and open Solutions. Click into the solution you care about, then look at the address bar. The bit right after solutions/ is your solution GUID, e.g. 3f2a91c7-8b04-4e6d-a1f9-2c7d5e8b04a1.
  2. Now go to your DEV environment’s actual application URL, something like https://your-env.crm4.dynamics.com/main.aspx?appid=<some-app-guid>, and wait for it to fully load. This isn’t busywork — it’s how you make sure you’re logged in and have a live session cookie for this environment. Everything below rides on that cookie.
  3. Keep the hostname, ditch the rest of the path, and append /api/data/v9.2/solutions(<SOLUTION_GUID>). So you’d end up at:
    https://your-env.crm4.dynamics.com/api/data/v9.2/solutions(3f2a91c7-8b04-4e6d-a1f9-2c7d5e8b04a1)
    Your browser will show you the raw solution record. Sanity-check that it’s the solution you think it is (turn on your browser’s “pretty print” for JSON if it looks like alphabet soup).
  4. Copy the uniquename value out of that record.

You now have the solution GUID and UniqueName. Two down.

Step 2: Identify the component

Next we need the component’s own record GUID (its objectid) and its ComponentType (a magic number Dataverse uses to know what kind of thing it is).

  1. Figure out the component’s record GUID. Say we’re chasing an App Setting, which lives in the appsettings table — its record GUID is appsettingid. If the UI is being difficult (which is the whole reason we’re here), you can find the record via the Web API directly, or reach for a tool like XrmToolBox‘s FetchXML Builder. Copy that record GUID.
  2. Now confirm the thing is actually a component of this solution. Query the relationship: /api/data/v9.2/solutions(<SOLUTION_GUID>)/solution_solutioncomponent?$select=componenttype,objectid&$filter=objectid eq <OBJECT_GUID>
    Tacked onto our example hostname:
    https://your-env.crm4.dynamics.com/api/data/v9.2/solutions(3f2a91c7-8b04-4e6d-a1f9-2c7d5e8b04a1)/solution_solutioncomponent?$select=componenttype,objectid&$filter=objectid eq b5d8e37a-4f21-4c9b-8e6d-9a3f7c2b1d54
    If you get back a JSON array ([ ]) with exactly one object ({ }) inside it, you’ve got the right component in the right solution. Zero objects means it’s not part of this solution; more than one means you’ve filtered wrong.
  3. Copy the componenttype value. It’ll be a number.

You now have the object GUID and the ComponentType. Everything we need is on the table.

Step 3: Remove the component

Here’s the part that feels slightly illegal but isn’t. Dataverse exposes a RemoveSolutionComponent action, and we’re just going to call it.

  1. Make sure you’ve got the solution UniqueName (from Step 1) and the object GUID + ComponentType (from Step 2) handy.
  2. In the same browser window where you’re logged in — the one with the live session cookie, remember — open the Developer Tools (F12) and switch to the Console tab. Reusing the tab where you already have an API call open is perfect.
  3. Paste in the following, swapping in your three values. Note that <OBJECT_COMPONENTTYPE> is a number, so it does not get quotes:
fetch("/api/data/v9.2/RemoveSolutionComponent", {
       method: "POST",
       body: JSON.stringify({
           "SolutionComponent": {
               "@odata.type": "Microsoft.Dynamics.CRM.solutioncomponent",
               "solutioncomponentid": "<OBJECT_GUID>"
           },
           "ComponentType": <OBJECT_COMPONENTTYPE>,
           "SolutionUniqueName": "<SOLUTION_UNIQUENAME>"
       }),
       headers: {
           "Content-Type": "application/json"
       }
   });
  1. Hit Enter. The command runs immediately — there’s no “are you sure?”, so this is your are-you-sure moment.
  2. Pop over to the Network tab and check the request came back with a status code of 200 OK. That’s Dataverse’s way of telling you the component is gone.

And that’s it. The component is out of the solution, the UI can stop gaslighting you about it, and you can go back to whatever you were actually supposed to be doing today.

A word on the “preferred solution” thing

If you’d rather not do this dance again, it’s worth remembering why the components landed there in the first place. The preferred solution is a convenience feature — new components get auto-added to it — but “convenient” and “what I wanted” aren’t always the same sentence, and the auto-adding happens silently and invisibly in the UI, which is the worst possible combination.

Here’s the annoying bit: as far as I can tell there’s no way to actually unset the preferred solution once one is chosen. You can only point it at a different solution. So my workaround was to create a throwaway dummy solution on DEV and set that as the preferred one. Now when Dataverse feels the urge to helpfully stash a component somewhere, it dumps it into the sacrificial solution I never ship, and my real solutions stay clean. It’s a bit of a hack, but it’s a lot cheaper than debugging another mystery import failure at the worst possible moment.

Right. Off to add actual business value instead of hunting ghosts.

Bol.com epub Adobe DRM verwijderen op een Mac

Ik had al enkele DRM (digital rights management)-vrije epub boeken gekocht bij Bol.com, stond op het punt een Engels boek te kopen, maar werd tegengehouden door het feit dat het een epub met “Adobe DRM” betrof. Ik lees mijn e-boeken namelijk in iBooks op mijn computer en telefoon. Epub bestanden met Adobe DRM werken daarin niet.

Een korte zoektocht leerde mij dat ik de DRM eenvoudig zou moeten kunnen verwijderen van de epub. Dat bleek uiteindelijk zo te zijn, ik had in eerste instantie een te oude versie Adobe Digital Editions geïnstalleerd.

Wat heb je nodig:

Stappenplan

  1. Download de software van bovenstaande lijst.
  2. Installeer Adobe Digital Editions versie 2.0.1 en autoriseer je Mac (hiervoor heb je een Adobe ID nodig).
  3. Installeer Calibre: open de gedownloade DMG en sleep Calibre.app naar je Applications map.
  4. Installeer de DeDRM Calibre plugin:
    1. Pak de DeDRM_tools_X.Y.Z.zip uit (op dit moment is X.Y.Z versie 6.5.1).
    2. Open Calibre en ga naar menu Preferences → Preferences.
    3. Klik in het scherm Preferences onder Advanced op Plugins.
    4. Klik in het scherm Plugins op de knop (rechtsonder) “Load plugin from file”.
    5. Navigeer naar de map met de eerder uitgepakte zip (DeDRM_tools_X.Y.Z) → DeDRM_calibre_plugin → DeDRM_plugin.zip en klik op Open.
    6. Klik op Apply.
    7. Sluit Calibre af (Quit).
  5. Download het e-boek van Bol.com (een .ACSM bestand)
  6. Open het .ACSM bestand, dit opent Adobe Digital Editions.
  7. Controleer of het gedownloade e-boek te lezen is in Adobe Digital Editions (zo niet, dan lukt het Calibre ook niet het boek te lezen).
  8. Sluit Adobe Digital Editions af.
  9. Open Calibre, klik op Add Books en navigeer in je gebruikers-/thuismap naar Documents → Digital Editions.
  10. Selecteer daar het betreffende epub bestand.
  11. Calibre verwijdert automatisch de Adobe DRM tijdens het toevoegen van het e-boek.
  12. Controleer of het boek te lezen valt in Calibre (anders werkt het ook niet in iBooks…)
  13. Klik met rechts op het boek in Calibre en kies “Open containing folder”.
  14. Het .epub bestand wat daar staat kun je openen met iBooks.

Veel leesplezier op een willekeurige e-reader gewenst!

T-SQL ISO 8601 Week, Year and Start Date

The ISO 8601 week dates standard seems very simple, but is hard to use in (T-)SQL. Let’s start with a couple of example dates:

  • 2012-01-01 (Sunday) is in 2011-W52, first date (Monday) is 2011-12-26
  • 2013-01-01 (Tuesday) is in 2013-W01, first date (Monday) is 2012-12-31
  • 2016-01-01 (Friday) is in 2015-W53, first date (Monday) is 2015-12-28

The easy part is getting the ISO week number from a datetime. Executing DATEPART(ISO_WEEK, '2012-01-01') yields 52, so that’s correct. But determining the ISO week-year is harder. YEAR('2012-01-01') yields 2012 which is correct as a date, but not for the ISO week-year (which is 2011, see example above).

The following determines the ISO week-year. First, Monday is set as the first day of the week. Than, the difference between Thursday (day number 4) and the given day of the week is added to the given date. The year of the calculated date (which is the Thursday in the week of the given date) is the ISO week-year.

-- Input
DECLARE @MyDate datetime = '2012-01-01';

-- Configuration
SET DATEFIRST 1; -- Monday is first day of the week

-- Execute
SELECT YEAR(DATEADD(DAY, (4 - DATEPART(WEEKDAY, @MyDate)), @MyDate))

The query above returns 2011, which is correct.

The following determines the first date of a given ISO week. When the first day of the given year is Monday, Tuesday, Wednesday or Thursday, it is in ISO week 1 of the given year. Otherwise, it is in the last ISO week of the previous year. Next, we determine the date of the Monday in the week in which the first day of the given year falls. Finally, we add the number of given ISO weeks to that date, corrected with whether the first day of the year is in week 1 or not.

-- Input
DECLARE @Year int = 2013;
DECLARE @Week int = 1;
 
-- Config
SET DATEFIRST 1; -- Monday is first day of the week
 
-- Calculate
DECLARE @FirstDayOfYear datetime = DATEFROMPARTS(@Year, 1, 1);
DECLARE @IsFirstDayInFirstWeek int = CASE WHEN (DATEPART(WEEKDAY, @FirstDayOfYear) <= 4) THEN 1 ELSE 0 END;
DECLARE @StartOfWeek datetime = DATEADD(WEEK, @Week - @IsFirstDayInFirstWeek, DATEADD(DAY, (1 - DATEPART(WEEKDAY, @FirstDayOfYear)), @FirstDayOfYear));

-- Execute
SELECT @StartOfWeek

The query above returns 2012-12-31, which is correct.

Take note that DATEFROMPARTS works from SQL Server version 2012, but that it can easily be replaced using string concatenation to implicitly create a datetime. Furthermore, SET DATEFIRST 1; has session-scope, so any date calculations during the SQL session may be affected!

Try the queries with the examples to see that they work. Hope this helps!

Fix blue tint on MacBook Pro dedicated GPU

This post is primarily a reminder for myself on how to purge/reset the ColorSync user cache.

I’ve got a mid-2010 15″ MacBook Pro, which was the first having both an integrated GPU (Intel HD) and a dedicated GPU (NVidia Geforce GT 330M). It “dynamically” switches between the two on-demand, which means some applications (e.g. Adobe Photoshop, Google Chrome, Visual Studio Code) trigger the switch, probably because of some low-level GPU call that makes OS X decide the dedicated GPU must be used.

Now, since a while, when the MBP would switch from the internal GPU (iGPU) to the dedicated GPU (dGPU), the screen would change colour. It would change from its correct colour to a somewhat blue-ish tint. Very bad on the eyes, especially in the evening.

After googling around a lot, a number of posts on the Apple Discussions pages recommend gfxCardStatus, using which you can “lock” the MBP on the iGPU. I consider this a bad option (it’s not bad software by the way, I’ve used it in the past to monitor the GPU switching), since the dGPU is far more powerful than the iGPU and often you need that power (be it the dedicated memory or the computation power). And, when you attach an external screen (like I do most of the times), it doesn’t work at all.

I also found the recommendation to reset or purge the ColorSync user cache. I tried this and it immediately worked, removing the blue-ish tint from the MBP screen (well, after removing the ColorSync cache and restarting the MBP).

The easiest way to do this (in my opinion), is described by Topher Kessler on CNET.com in his post titled “How to make ColorSync profiles stick in OS X”. It took me a while to find it because the title doesn’t immediately give away the fact that it concerns the ColorSync cache.

So, long story short, execute the following in the Terminal:

sudo rm $(getconf DARWIN_USER_CACHE_DIR)com.apple.colorsync.profiles.`id -u`

…which gets rid of the existing ColorSync user cache. Restart afterwards and the screen will maintain the same colour on both the iGPU and the dGPU.

I don’t know why or how the ColorSync user cache gets corrupted, but I do know how to solve it!

DYMO Label Framework Typescript Definition

About a week ago I’ve created a Typescript definition file (.d.ts) for the DYMO Label Framework. Being the good netizen I am, I created a pull request on DefinitelyTyped (the de facto Typescript definition repository) and it was accepted! Well, the second time round it was…

So, using the amazing tsd tool (kind of like the bower for typescript definition files), you can now do…

$ tsd install dymo-label-framework

…and have the Typescript definition file automatically added to your project. This will give you Intellisense in e.g. Visual Studio Code or VS2015 and compile-time type checking during Typescript to javascript compilation.

I’ve done my best to add all documentation to the definition file, so you can enjoy proper Intellisense with full documentation during programming. Enjoy!

Remove hyphenation from WordPress Twenty Fifteen theme

After using the same WordPress theme since 2007, the time had come to switch. I quite like the default Twenty Fifteen theme, so I switched to that. I immediately noticed words in the site started automatically hyphenating. I don’t like that.

Continue reading Remove hyphenation from WordPress Twenty Fifteen theme

Assembly.Load and FileNotFoundException

I’m experimenting with AppDomains to be able to load multiple versions of the same assembly in one application. I’ll write a bigger post about my findings later, but there’s one thing I encountered very early in the experiments: using Assembly.Load on a newly created AppDomain immediately leads to a FileNotFoundException. It took me a while to figure out why that happened and I’d like to share my experience with you.
Continue reading Assembly.Load and FileNotFoundException

Verify the integrity of a flash/SD card on a Mac

Last minute camera shopping

The week before going on a holiday, I realised my old Canon Ixus 40 camera would no longer live up to my expectations. So I quickly checked dpreview.com for the latest and greatest compact camera’s and decided I wanted the Canon PowerShot SX240 HS. 15 Minutes later I found myself in the Media Markt with the camera in my hand (unfortunately only the silver colour was available, I had hoped to buy the black one).

I need a bigger memory card

I then realised that my existing 512MB and 1GB SD cards would no longer be large enough for the 12Mpixel photos of this new camera. A few meters down the corridor was a huge wall covered in memory cards. I decided to buy a Samsung 8GB SDHC card, which would be able to hold up to 1500 photos or an hour of HD video. When I returned home, I formatted the card in-camera, and played around with the photo and video functions. Three days later my holiday would start and I would have a good (enough) camera to capture some fine moments!

Corrupted photos

Fast-forward about two weeks, half-way into my holiday. I’ve happily been taking photos and videos with my new camera. While I’m reviewing the last five photos, the camera suddenly displays a “broken picture” image on the screen. I take another photo, review it and it’s immediately “broken” again. I’m approximately on two-thirds of the card’s capacity and it is corrupting my photos. I do what I’ve learned from experience with corrupt memory cards: turn off the camera immediately, remove the SD card, and to be sure, “lock” the card (making it read-only) using the physical switch on the SD card. At this point, I just hope for the best that only those last two photos were corrupted.

Back at home a week-and-a-half later, it turns out I’m extremely lucky! Indeed only those last two pictures were corrupted! Continue reading Verify the integrity of a flash/SD card on a Mac

Profiling on SQL Server Express

I’m working on a new web app using ASP.NET MVC 3 and Entity Framework 4.1. One thing lacking in EF 4.1 is the ability to monitor the actual queries sent to the SQL database, like you could do with Linq2SQL’s DataContext.Log.

The machine I’m working on has SQL Server Express. The Express version doesn’t come with the SQL Profiler. So without Profiler and without a log function on Entity Framework’s DbContext, what’s a developer to do?

Well, as it turns out, profiling is part of the SQL Server (any version, including Express), the SQL Profiler application is just one way to access the profiling functionality. Some people even argue it’s better to do the profiling on the server itself, as opposed to through SQL Profiler.

Starting the trace

How do you start? The easiest way to start is (you’re not going to like this) by using SQL Profiler. That’s what I did (on another machine). It’s explained on the website linked above, here are the steps: Continue reading Profiling on SQL Server Express

Fighting “General SQL Error” in Dynamics CRM custom workflows

Working for my client Valx, I had some serious problems in custom workflows for Microsoft Dynamics CRM 4. Every now and then, a workflow would throw a SoapException. I always put the original exception in the InvalidPluginExecutionException (second parameter “inner exception”). Now the actual exception information you want to see is in SoapException.Detail, which is an XmlNode. You won’t see that info if you just put the SoapException in the InvalidPluginExecutionException’s inner exception. Therefore, I use the following try/catch construction in each custom workflow’s Execute method to see some relevant information in the WorkflowLog view of the Dynamics CRM database.

Continue reading Fighting “General SQL Error” in Dynamics CRM custom workflows