Ubuntu Jammy casualties: dotnet

Before I upgraded to Ubuntu 22.04 (Jammy), I had dotnet 5 and 6 installed, rather out of interest, than doing any serious programming.

As far as I can remember, I originally had installed the Snap version of dotnet, but had troubles installing any dotnet packages or templates, so I had switched to the “native” version of dotnet, probably using the dotnet-install.sh variant – I’m not sure.

Upgrading to 22.04 seems to have uninstalled dotnet 5, as it is out of support, and weird things happened to the dotnet 6 installation:

Running

dotnet new -i Avalonia.Templates

did not install the expected templates, but rather immediately returned the error message

The application ‘new’ does not exist

Further, the command dotnet --info listed two 6.0 runtimes, but did not know of any previously installed SDKs.

After uninstalling and re-installing and uninstalling the dotnet6 package, the command dotnet was still there, but did not do anything useful.

$ dotnet --list-sdks
A fatal error occurred. The folder [/usr/lib/dotnet/dotnet6-6.0.108/host/fxr] does not exist

This error message got me to dotnet/core Issue 5746 (dealing with .Net Core 3 on CentOS though), where a comment states

This can happen if dotnet-host and dotnet-hostfxr-3.1 are conflicting,

So I queried apt for the list of installed dotnet packages

$ apt list | grep dotnet

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

dotnet-apphost-pack-5.0/now 5.0.17-1 amd64 [installed,local]
dotnet-apphost-pack-6.0/jammy-updates,jammy-security,now 6.0.109-0ubuntu1~22.04.1 amd64 [installed,auto-removable]
dotnet-host/jammy-updates,jammy-security 6.0.109-0ubuntu1~22.04.1 amd64 [upgradable from: 6.0.108-0ubuntu1~22.04.1]
dotnet-hostfxr-5.0/now 5.0.17-1 amd64 [installed,local]
dotnet-hostfxr-6.0/jammy-updates,jammy-security,now 6.0.109-0ubuntu1~22.04.1 amd64 [installed,auto-removable]
dotnet-runtime-6.0/jammy-updates,jammy-security 6.0.109-0ubuntu1~22.04.1 amd64
dotnet-runtime-deps-5.0/now 5.0.17-1 amd64 [installed,local]
dotnet-sdk-6.0-source-built-artifacts/jammy-updates,jammy-security 6.0.109-0ubuntu1~22.04.1 amd64
dotnet-sdk-6.0/jammy-updates,jammy-security 6.0.109-0ubuntu1~22.04.1 amd64
dotnet-targeting-pack-5.0/now 5.0.0-1 amd64 [installed,local]
dotnet-targeting-pack-6.0/jammy-updates,jammy-security,now 6.0.109-0ubuntu1~22.04.1 amd64 [installed,auto-removable]
dotnet-templates-6.0/jammy-updates,jammy-security,now 6.0.109-0ubuntu1~22.04.1 amd64 [installed,auto-removable]
dotnet6/jammy-updates,jammy-security 6.0.109-0ubuntu1~22.04.1 amd64
libgtk-dotnet3.0-cil-dev/jammy 2.99.3-4build1 amd64
libgtk-dotnet3.0-cil/jammy 2.99.3-4build1 amd64

So I manually uninstalled all remaining dotnet packages

$ sudo apt-get remove dotnet-hostfxr-5.0
$ sudo apt autoremove

until the dotnet command was no longer recognized. From this clean state, I ran

$ sudo apt install dotnet6

and got a brand-new dotnet 6 environment and even a new application.

$ dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.109
 Commit:    58a93139d8

Runtime Environment:
 OS Name:     ubuntu
 OS Version:  22.04
 OS Platform: Linux
 RID:         ubuntu.22.04-x64
 Base Path:   /usr/lib/dotnet/dotnet6-6.0.109/sdk/6.0.109/

global.json file:
  Not found

Host:
  Version:      6.0.9
  Architecture: x64
  Commit:       163a63591c

.NET SDKs installed:
  6.0.109 [/usr/lib/dotnet/dotnet6-6.0.109/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.9 [/usr/lib/dotnet/dotnet6-6.0.109/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.9 [/usr/lib/dotnet/dotnet6-6.0.109/shared/Microsoft.NETCore.App]

File Organizers: Calibre

A long time ago, I had a look at Calibre to organize my e-books, but chose not to use it mainly because of one issue: Calibre uses its own directory structure to organize its e-books (library/author/title (id)), and also renames the files accordingly (truncated title - author.extension).

How should I ever find a book again, I thought, if the tool ignores my structure of sorting files? After all, I already created some kind of order on my file system, so that I should know where my books are.

Well, in the course of cleaning up a soon-to-be shut-down machine, I noticed that my e-book collection instead was really a huge mess, spread over several machines, discs, and directories, full of duplicates and copies – recognizable by file name or file size.

So, let’s throw it all on Calibre.

The nice thing about Calibre is that it stores its metadata not in the e-books, but in a local SQLite database and a metadata.opf OPF file for every book.

Also, using tags to organize books is more flexible and powerful as compared to the tree-like directory hierarchy of traditional file systems.

Still, I wanted to keep the original filename of any catalogued e-book, so I added the GetFileName plugin which automatically adds the original filename in a separate user-defined column.

To keep track of all the books I always wanted to read, and those I have already read, I installed the Reading List plugin.

Since Calibre warns you if you want to import a book already imported, and I always ignore those warnings, a lot of duplicates are going to end up in my e-book library. That’s where the Find Duplicates plugin is going to be helpful in the future.

Until then, a generic [todo] tag adorns all my Calibre books until the metadata is cleaned up.

Though still early in my book-cataloguing adventure, I already stumble across problems I still need to figure out.

  • How do you handle different spellings of an author’s name (plain name, with middle initial, with middle name) or pseudonyms?
  • How do you import HTML books spread over several pages?
  • How do you handle e-books split into chapter-wise PDFs?

Not all updates can be installed

If you run Ubuntu, you’ll notice that every now and then the Software Updater will provide you with a notification

Updated software is available for this computer. Do you want to install it now?

Except, when it does not, and instead displays a dialog

Software Updater: Not all updates can be installed

with a set of rather unintuitive buttons.

What is most disturbing is the list of possible causes, when it’s just a piece of software that checked all kinds of error conditions and should know what went wrong, and should be able to detail the error condition that prevents it from resuming its task, rather than the lazy “An error occurred”.

So I started to track the message down, and found:

  • Software Updater is a Python/GTK application residing in /usr/bin/update-manager
  • The application imports code from the UpdateManager package under /usr/lib/python3/dist-packages/UpdateManager
  • There, the MyCache class in Core/MyCache.py checks for various error conditions, and presents above dialog if something went wrong

It should be easy to adapt the code to display those error conditions. After a bit of trial and error, my version of the MyCache class displayed what kept the Software Updater from updating:

$ ./CheckUpdateManager.py
initializing
checking
would delete nodejs-doc
that's all

After removing the listed package

$ sudo apt-get remove nodejs-doc

Software Updater displayed its usual window of updatable software components.

The script CheckUpdateManager.py is available in my Python repository.

Fixing Error NU1202: Package Microsoft.EntityFrameworkCore.Tools 6.0.1 is not compatible with netcoreapp2.2 (.NETCoreApp,Version=v2.2)

Migrating an AspNetCore web and database application from version 2.2 to current .Net 6, I came across an occasional build error:

error NU1202: Package Microsoft.EntityFrameworkCore.Tools 6.0.1 is not compatible with netcoreapp2.2 (.NETCoreApp,Version=v2.2). Package Microsoft.EntityFrameworkCore.Tools 6.0.1 supports: net6.0 (.NETCoreApp,Version=v6.0) 

The error went away by unchecking the Option “Allow NuGet to download missing packages” under “Nuget Package Manager”, so I did not bother any longer.

Unfortunately, the error returned as soon as I ran an Azure Build Task on the project. The task failed with a simple message

##[error]Packages failed to restore

without any indication what exactly failed, what was to be restored, and, especially, why.

Going through the log file, I found that nuget tried to restore packages for .Net 6 (good), but also für .NetCore 2.2 (bad):

2022-04-06T13:59:20.3799748Z          Restoring packages for .NETCoreApp,Version=v6.0...
2022-04-06T13:59:20.4022386Z          Resolving conflicts for .NETCoreApp,Version=v2.2...
2022-04-06T13:59:20.4605261Z          Resolving conflicts for net6.0-windows7.0...
2022-04-06T13:59:20.5840720Z          Checking compatibility of packages on .NETCoreApp,Version=v2.2.
2022-04-06T13:59:20.5886337Z          Checking compatibility for microsoft.entityframeworkcore.tools-netcoreapp2.2-[6.0.1, ) 1.0.0 with .NETCoreApp,Version=v2.2.
2022-04-06T13:59:20.6014642Z          Checking compatibility for Microsoft.EntityFrameworkCore.Tools 6.0.1 with .NETCoreApp,Version=v2.2.
2022-04-06T13:59:20.6016218Z      1>D:\DevOpsAgents\some\path\Project.Data.csproj : error NU1202: Package Microsoft.EntityFrameworkCore.Tools 6.0.1 is not compatible with netcoreapp2.2 (.NETCoreApp,Version=v2.2). Package Microsoft.EntityFrameworkCore.Tools 6.0.1 supports: net6.0 (.NETCoreApp,Version=v6.0) 

I also noticed that the first lines of the log stated

Info: .NET Core SDK/runtime 2.2 and 3.0 are now End of Life(EOL) and have been removed from all hosted agents. If you're using these SDK/runtimes on hosted agents, kindly upgrade to newer versions which are not EOL, or else use UseDotNet task to install the required version.
[command]"C:\Program Files\dotnet\dotnet.exe" restore D:\DevOpsAgents\some\path\Project.sln

By that time, I had verified multiple times that the projects do not use any obsolete 2.2 packages anymore. Where does the reference come from?

Then I remembered that I had experienced *some *sort of build errors, as stated above, and tried to run a dotnet restore on my development machine, disabling parallel execution and turning verbosity to full diagnostics:

dotnet restore my.project.sln --disable-parallel -v diag > dotnet-restore.log

The output is huuuge, so I piped it to a file.

Searching the generated log file for netcoreapp2.2, I finally located these lines

17:37:29.274 10:4>_GenerateDotnetCliToolReferenceSpecs: (Target-ID:26)
    TaskParameter:ToolFramework=netcoreapp2.2 (Task-ID: 17)

I found the target _GenerateDotnetCliToolReferenceSpecs in the file C:\Program Files\dotnet\sdk\6.0.101\NuGet.targets, and it sets the DotnetCliToolTargetFramework build variable to a default value if it is not set already. The default value may be, depending on your installation, netcoreapp1.0, netcoreapp2.2, or some other version.

Which part of the project would trigger an action referring to dotnet CLI tools? It’s the EF Core Tools I needed to install for database migrations:

  <ItemGroup>
	  <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1"></DotNetCliToolReference>
  </ItemGroup>

This project setting causes dotnet to install the referenced tool, but fails to do so because the default tool framework is incompatible.

This observed behavior is documented in this GitHub issue for NuGet:

  • The DotnetCliToolTargetFramework will be interpreted by NuGet restore as the maximum target framework that tools can be restored for
  • When restoring .NET CLI tools, NuGet will first download the package, and inspect it to see what it targets. It will choose the maximum target framework the package explicitly supports that is less than or equal to the DotnetCliToolTargetFramework

This GitHub issue showed me how to set the framework version, so I added to my csproj file the following lines:

  <PropertyGroup>
    <DotnetCliToolTargetFramework>net6.0</DotnetCliToolTargetFramework>
  </PropertyGroup>

and dotnet restore worked without errors.

Disabling Loopback for Logitech G432 on Ubuntu

My old Logitech headset died a number of deaths (headband broken, cushion torn, finally cable broken), so I got myself a G432.

Unfortunately, using it in Teams under Ubuntu, I experienced an irritating loopback. Quickly took to the internets, but AskUbuntu was only of limited help.

For example, one answer suggested to use alsamixer, but that only completely muted the headset, not only the microphone loopback.

Another answer pointed me to a tool called HeadsetControl which allows exactly the control I wanted, and its Readme file even lists the model I have. Hurray.

I followed the instructions to install:

sudo apt-get install build-essential git cmake libhidapi-dev
git clone https://github.com/Sapd/HeadsetControl && cd HeadsetControl
mkdir build && cd build
cmake ..
make
sudo make install

After installation, I ran

$ headsetcontrol -?
Found Logitech G432/G433!
Supported capabilities:

* sidetone

And tried to set “sidetone” to zero:

headsetcontrol -s 0
Found Logitech G432/G433!
Failed to open requested device.
HID Error: (null)

Apparently, headsetcontrol requires sudo:

sudo headsetcontrol -s 0
Found Logitech G432/G433!
Success!

No more loopback!

Wordle cheat script

An article on The Register linked to a GitHub shell script version of Wordle, and what caught my eye was that apparently a Linux installation contains a dictionary of English words in the file /usr/share/dict/words.

If you are stuck in your daily Wordle, you can use this file to figure out solutions:

CommandPurpose
grep -E '^([a-zA-Z]){5}$' /usr/share/dict/wordscollect all 5-letter words
tr '[a-z]' '[A-Z]'convert words to upper case
grep 'RU...'filter words for matching (green) letters
grep '[E]'filter words for letters in the wrong spot (yellow)
grep -v '[ASD]'exclude known wrong letters (dark grey)

Combine the commands (the last 3 are optional) with the respective letters filled in, separated by the pipe “|”:

$ grep -E '^([a-zA-Z]){5}$' /usr/share/dict/words | tr '[a-z]' '[A-Z]' | grep 'RU.E.' | grep -v '[TASDLMN]'
RUPEE

Angular Google Maps Demo

The Angular Components documentation on its Google Maps component does not contain a working demo. Probably that’s because it would need to incorporate a YOUR_API_KEY value…

So I took the sample code from their README.md and README.md files and stitched together a working (uhm api key) sample on StackBlitz.

I noticed that while my code works find on my local machine, the StackBlitz version will raise an error in the console

Error in src/app/google-maps-demo/google-maps-demo.component.ts (14:11)
Cannot find namespace ‘google’.

This error is caused by the fact that the namespace google.maps is only created if the Google Maps API is loaded successfully.

Which brings me to error handling:

Essentially there are 3 cases that need to be dealt with:

  • Success
  • Invalid API key
  • Invalid URL for the Google Maps API call

In case of an invalid API key, the <google-map> component raises the authFailure event

In case of an invalid URL, there is no script to initialize the google namespace, and you can check whether the objects window["google"] and window["google"]["maps"] exist.

The code is also available as a GitHub repository.

ng update will update you automatically

During my Angular Update Odyssey, I found that the Angular Update Guide frequently states

ng update will do this automatically for you.
ng update should take care of this automatically.
ng update will migrate you automatically.

and I wondered when ng update would actually perform all those magic tasks, because I did not notice them.

Then I had the relevation, that ng update only performs the update tasks, if the package update was successful.

If the package update fails due to package dependency conflicts, then no update magic. Bad luck. You need to clean-up by yourself.

Analyzing the update log I keep (essentially a copy-and-paste from the npx update console output), this is what happens:

> npx @angular/cli@10 update @angular/core@10 @angular/cli@10

The installed local Angular CLI version is older than the latest stable version.
Installing a temporary version to perform the update.
Installing packages for tooling via npm.
Installed packages for tooling via npm.
Using package manager: 'npm'
Collecting installed dependencies...
Found 54 dependencies.

Fetching dependency metadata from registry...
    Updating package.json with dependency @angular-devkit/build-angular @ "0.1002.4" (was "0.901.15")...
    Updating package.json with dependency @angular/cli @ "10.2.4" (was "9.1.15")...
...

  UPDATE package.json (2249 bytes)

npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR!
npm ERR! While resolving: my.web.app@1.0.0
npm ERR! Found: @angular-devkit/build-angular@0.901.15
npm ERR! node_modules/@angular-devkit/build-angular
npm ERR!   dev @angular-devkit/build-angular@"^0.1002.4" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! dev @angular-devkit/build-angular@"^0.1002.4" from the root project
npm ERR!
npm ERR! Conflicting peer dependency: @angular/compiler-cli@10.2.5
npm ERR! node_modules/@angular/compiler-cli
npm ERR!   peer @angular/compiler-cli@"^10.0.0" from @angular-devkit/build-angular@0.1002.4
npm ERR!   node_modules/@angular-devkit/build-angular
npm ERR!     dev @angular-devkit/build-angular@"^0.1002.4" from the root project

My interpretation of the log entries is that

  • ng update fetches the dependencies of the new packages
  • ng update writes package.json with new version numbers
  • npm fails to install because of dependency conflicts
  • ng update gives up, because the new packages are not there

If you’re lucky, the node_modules directory has been deleted, too.

So, for future updates, I will consider the following update strategy:

  • run npx update
  • if the update fails as above, revert the changes in package.json
    – apparently there’s a reason npx update warns

Repository is not clean. Please commit or stash any changes before updating.

  • run npx update --force
  • commit changes
  • fix broken dependencies

mat- is not a known element

Updating an ancient Angular application to a more current version, I came across various warnings and error relating to the Angular Material framework.

Hidden behind the inconspicuous comment

Instead of importing from @angular/material, you should import deeply from the specific component. E.g. @angular/material/button

https://update.angular.io/?v=8.0-9.0

is the task of adding all kinds of import declarations to the modules containing components with Material elements or attributes.

In case of missing elements, the browser console will display a warning

‘mat-xxx’ is not a known element:
1. If ‘mat-xxx’ is an Angular component, then verify that it is part of this module.
2. If ‘mat-xxx’ is a Web Component then add ‘CUSTOM_ELEMENTS_SCHEMA to the ‘@NgModule.schems’ of this component to suppress this message.

In case of missing attributes, the browser console will display a warning

Can’t bind to ‘mat-xxx’ since it isn’t a known property of ‘<element>’

  • First, an import declaration in the module’s .ts file:
    import { MatXxxxModule } from '@angular/material/xxxx';
  • Second, adding the MatXxxxModule in the imports array of modules in the @NgModule declaration
Console Error MessageImport ModuleModule Location
mat-autocomplete is not a known elementMatAutocompleteModuleautocomplete
[matBadge]MatBadgeModulebadge
[mat-button]MatButtonModulebutton
mat-chip-list is not a known elementMatChipsModulechips
mat-dialog is not a known elementMatDialogModuledialog
mat-divider is not a known elementMatDividerModuledivider
mat-form-field is not a known elementMatFormFieldModuleform-field
mat-icon is not a known elementMatIconModuleicon
mat-list is not a known elementMatListModulelist
mat-menu is not a known elementMatMenuModulemenu
mat-paginator is not a known elementMatPaginatorModulepaginator
mat-progress-bar is not a known elementMatProgressBarModuleprogress-bar
mat-select is not a known elementMatSelectModuleselect
[mat-sort-header]MatSortModulesort
mat-spinner is not a known elementMatProgressSpinnerModuleprogress-spinner
mat-tab is not a known elementMatTabsModuletabs
mat-table is not a known elementMatTableModuletable
mat-toolbar is not a known elementMatToolbarModuletoolbar
[matToolTip]MatTooltipModuletooltip

Switching Sound Output between Headset and Speakers

How do you change the sound device a particular application is using for its audio output? I never bothered to think about this question, until the times of the shared home office.

True, in Windows you can change the default sound device, and an application that is started afterwards will use that device. But changing the sound device for a running application? It seems there is no built-in way to do that.

For Windows 7 and higher, I found the SoundSwitch applet (GitHub), which does just that. Located in the notification area, it lets you select the sound device the current application outputs its audio to. Simply click on its icon and select the device, or press a customizable hotkey.

For Ubuntu, I found Sound Switcher Indicator (GitHub, Blog). Upon installation, it can be accessed from the indicator area, and allows the selection of audio input and output devices.

Problems solved 😉

Epilogue

During preparation of this post, I tried to figure out how SoundSwitch actually switches the audio device.

It turns out that it uses the function call SetPersistedDefaultAudioEndpoint() which is also used by an application called EarTrumpet (GitHub) that tags itself as “Volume control for Windows”, but also seems to allow switching audio devices.

A review of this application by Scott Hanselman indicates that the method and its interface are not officially documented:

Internal Audio Interface: IAudioPolicyConfigFactory

Gets them access to new APIs (GetPersistedDefaultAudioEndpoint / SetPersistedDefaultAudioEndpoint) in RS4 that let’s them ‘redirect’ apps to different playback devices. Same API used in modern sound settings.

Code here with no public API yet?