I got a very strange warning recently when building a .NET Core app with "dotnet build."
MSB3246: Resolved file has a bad image, no metadata, or is otherwise inaccessible. Image is too small.
Eek! It's clear, in that something is "too small" but what? A file I guess? Maybe it's the wrong size?
The error code is MSB3246 which is nice and googleable/searchable but it was confusing because I couldn't figure our what file specifically. It just felt vague.
BUT!
I had recently been overclocking my machine (overly aggressively, gulp, about 40%) and had a very nasty hard reboot. As a result I had a few dozen files get orphaned - specifically the files were zero'ed out! Zero is small, right?
Turns out you can pass parameters over to MSBuild from "dotnet build" and see what MSBuild is doing internally. For example, you could
/fileLoggerParameters:verbosity=diagnostic
but that's long. So how about:
dotnet build /flp:v=diag
Cool. What deep logging do I see now?
Primary reference "deliberately.zero.bytes.dll". (TaskId:41) 13:36:52.397 1:7>C:\Program Files\dotnet\sdk\2.1.400\Microsoft.Common.CurrentVersion.targets(2110,5): warning MSB3246: Resolved file has a bad image, no metadata, or is otherwise inaccessible. Image is too small. [S:\work\zero-byte-ref\zero-byte-ref.csproj] Resolved file path is "S:\work\zero-byte-ref\deliberately.zero.bytes.dll". (TaskId:41) Reference found at search path location "{RawFileName}". (TaskId:41)
Now with "verbose" turned on I can see that one of the references is zero'ed out/corrupted/bad. I reinstalled .NET Core in my case and doubled checked all the DLLs/Assemblies that I was bringing in - I also ran chkdsk /f - and I was back in business!
I hope this helps someone who might stumble on error MSB3246 and wonder what's up.
Sponsor: Preview the latest JetBrains Rider with its built-in spell checking, initial Blazor support, partial C# 7.3 support, enhanced debugger, C# Interactive, and a redesigned Solution Explorer.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
Back in March of 2017 I blogged about Zeit and their cool deployment system "now." Zeit will take any folder and deploy it to the web easily. Better yet if you have a Dockerfile in that folder as Zeit will just use that for the deployment.
Zeit's free Open Source account has a limit of 100 megs for the resulting image, and with the right Dockerfile started ASP.NET Core apps are less than 77 megs. You just need to be smart about a few things. Additionally, it's running in a somewhat constrained environment so ASP.NET's assumptions around FileWatchers can occasionally cause you to see errors like
at System.IO.FileSystemWatcher.StartRaisingEvents() Unhandled Exception: System.IO.IOException: The configured user limit (8192) on the number of inotify instances has been reached. at System.IO.FileSystemWatcher.StartRaisingEventsIfNotDisposed(
While this environment variable is set by default for the "FROM microsoft/dotnet:2.1-sdk" Dockerfile, it's not set at runtime. That's dependent on your environment.
Here's my Dockerfile for a simple project called SuperZeit. Note that the project is structured with a SLN file, which I recommend.
Let me call our a few things.
First, we're doing a Multi-stage build here.
The SDK is large. You don't want to deploy the compiler to your runtime image!
Second, the first copy commands just copy the sln and the csproj.
You don't need the source code to do a dotnet restore! (Did you know that?)
Not deploying source means that your docker builds will be MUCH faster as Docker will cache the steps and only regenerate things that change. Docker will only run dotnet restore again if the solution or project files change. Not the source.
Third, we are using the aspnetcore-runtime image here. Not the dotnetcore one.
That means this image includes the binaries for .NET Core and ASP.NET Core. We don't need or want to include them again.
If you were doing a publish with a the -r switch, you'd be doing a self-contained build/publish. You'd end up copying TWO .NET Core runtimes into a container! That'll cost you another 50-60 megs and it's just wasteful.
Finally, since some container systems like Zeit have modest settings for inotify instances (to avoid abuse, plus most folks don't use them as often as .NET Core does) you'll want to set ENV DOTNET_USE_POLLING_FILE_WATCHER=true which I do in the runtime image.
So starting from this Dockerfile:
FROM microsoft/dotnet:2.1-sdk-alpine AS build WORKDIR /app
# copy csproj and restore as distinct layers COPY *.sln . COPY superzeit/*.csproj ./superzeit/ RUN dotnet restore
# copy everything else and build app COPY . . WORKDIR /app/superzeit RUN dotnet build
FROM build AS publish WORKDIR /app/superzeit RUN dotnet publish -c Release -o out
FROM microsoft/dotnet:2.1-aspnetcore-runtime-alpine AS runtime ENV DOTNET_USE_POLLING_FILE_WATCHER=true WORKDIR /app COPY --from=publish /app/superzeit/out ./ ENTRYPOINT ["dotnet", "superzeit.dll"]
Remember the layers of the Docker images, as if they were a call stack:
Your app's files
ASP.NET Core Runtime
.NET Core Runtime
.NET Core native dependencies (OS specific)
OS image (Alpine, Ubuntu, etc)
For my little app I end up with a 76.8 meg image. If want I can add the experimental .NET IL Trimmer. It won't make a difference with this app as it's already pretty simple but it could with a larger one.
BUT! What if we changed the layering to this?
Your app's files along with a self-contained copy of ASP.NET Core and .NET Core
.NET Core native dependencies (OS specific)
OS image (Alpine, Ubuntu, etc)
Then we could do a self-Contained deployment and then trim the result! Richard Lander has a great dockerfile example.
See how he's doing the package addition with the dotnet CLI with "dotnet add package" and subsequent trim within the Dockerfile (as opposed to you adding it to your local development copy's csproj).
FROM microsoft/dotnet:2.1-sdk-alpine AS build WORKDIR /app
# copy csproj and restore as distinct layers COPY *.sln . COPY nuget.config . COPY superzeit/*.csproj ./superzeit/ RUN dotnet restore
# copy everything else and build app COPY . . WORKDIR /app/superzeit RUN dotnet build
FROM build AS publish WORKDIR /app/superzeit # add IL Linker package RUN dotnet add package ILLink.Tasks -v 0.1.5-preview-1841731 -s https://dotnet.myget.org/F/dotnet-core/api/v3/index.json RUN dotnet publish -c Release -o out -r linux-musl-x64 /p:ShowLinkerSizeComparison=true
FROM microsoft/dotnet:2.1-runtime-deps-alpine AS runtime ENV DOTNET_USE_POLLING_FILE_WATCHER=true WORKDIR /app COPY --from=publish /app/superzeit/out ./ ENTRYPOINT ["dotnet", "superzeit.dll"]
Now at this point, I'd want to see how small the IL Linker made my ultimate project. The goal is to be less than 75 megs. However, I think I've hit this bug so I will have to head to bed and check on it in the morning.
However, if you check the comments in the Docker file and just use the a "FROM microsoft/dotnet:2.1-aspnetcore-runtime-alpine AS runtime" it works fine. I just think I can get it even smaller than 75 megs.
Talk so you soon, Dear Reader! (I'll update this post when I find out about that bug...or perhaps my bug!)
UPDATE2: After some bugs found and some hard work by our friends at Zeit it looks like the inotify issue in the sentence above has been fixed. Looks like it was a misconfiguration - which is great! I was worried there was a larger architectural issue but there isn't.
Sponsor: Preview the latest JetBrains Rider with its built-in spell checking, initial Blazor support, partial C# 7.3 support, enhanced debugger, C# Interactive, and a redesigned Solution Explorer.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
I'm reading a new chapter of The Imposter's Handbook: Season 2 that Rob and I are working on. He's digging into the internals of what's exactly in your SSH Key.
I generated a key with no password:
ssh-keygen -t rsa -C scott@myemail.com
Inside the generated file is this text, that we've all seen before but few have cracked open.
The private key is an ASN.1 (Abstract Syntax Notation One) encoded data structure. It's a funky format but it's basically a packed format with the ability for nested trees that can hold booleans, integers, etc.
However, ASN.1 is just the binary packed "payload." It's not the "container." For example, there are envelopes and there are letters inside them. The envelope is the PEM (Privacy Enhanced Mail) format. Such things start with ----- BEGIN SOMETHING ----- and end with ----- END SOMETHING ------. If you're familiar with BASE64, your spidey sense may tell you that this is a BASE64 encoded file. Not everything that's BASE64 turns into a friendly ASCII string. This turns into a bunch of bytes you can view in HEX.
We can first decode the PEM file into HEX. Yes, I know there's lots of ways to do this stuff at the command line, but I like showing and teaching using some of the many encoding/decoding websites and utilities there are out there. I also love using https://cryptii.com/ for these things as you can build a visual pipeline.
This ASN.1 JavaScript decoder can take the HEX and parse it for you. Or you can that ASN.1 packed format at the *nix command line and see that there's nine big integers inside (I trimmed them for this blog).
An RSA private key shall have ASN.1 type RSAPrivateKey:
RSAPrivateKey ::= SEQUENCE { version Version, modulus INTEGER, -- n publicExponent INTEGER, -- e privateExponent INTEGER, -- d prime1 INTEGER, -- p prime2 INTEGER, -- q exponent1 INTEGER, -- d mod (p-1) exponent2 INTEGER, -- d mod (q-1) coefficient INTEGER -- (inverse of q) mod p }
I found the description for how RSA works in this blog post very helpful as it uses small numbers as examples. The variable names here like p, q, and n are agreed upon and standard.
The fields of type RSAPrivateKey have the following meanings: o version is the version number, for compatibility with future revisions of this document. It shall be 0 for this version of the document. o modulus is the modulus n. o publicExponent is the public exponent e. o privateExponent is the private exponent d. o prime1 is the prime factor p of n. o prime2 is the prime factor q of n. o exponent1 is d mod (p-1). o exponent2 is d mod (q-1). o coefficient is the Chinese Remainder Theorem coefficient q-1 mod p.
Let's look at that first number q, the prime factor p of n. It's super long in Hexadecimal.
Rob and I are finding it really cool to dig just below the surface of common things we look at all the time. I have often opened a key file in a text file but never drawn a straight and complete line through decoding, unpacking, decoding, all the way to a math mathematical formula. I feel I'm filling up some major gaps in my knowledge!
Sponsor: Preview the latest JetBrains Rider with its built-in spell checking, initial Blazor support, partial C# 7.3 support, enhanced debugger, C# Interactive, and a redesigned Solution Explorer.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
This post won't be well organized so lower your expectations first. When Rob Conery first wrote "The Imposter's Handbook" I was LOVING IT. It's a fantastic book written for imposters by an imposter. Remember, I'm the original phony.
Now he's working on The Imposter's Handbook: Season 2 and I'm helping. The book is currently in Presale and we're releasing PDFs every 2 to 3 weeks. Some of the ideas from the book will come from blog posts like or similar to this one. Since we are using Continuous Delivery and an Iterative Process to ship the book, some of the blog posts (like this one) won't be fully baked until they show up in the book (or not). See how I equivocated there? ;)
The next "Season" of The Imposter's Handbook is all about the flow of information. Information flowing through encoding, encryption, and transmission over a network. I'm also interested in the flow of information through one's brain as they move through the various phases of being a developer. Bear with me (and help me in the comments!).
I was recently on a call with two other developers, and it would be fair that we were of varied skill levels. We were doing some HTML and CSS work that I would say I'm competent at, but by no means an expert. Since our skill levels didn't fall on a single axis, we'd really we'd need some Dungeons & Dragon's Cards to express our competencies.
I might be HTML 8, CSS 6, Computer Science 9, Obscure Trivia 11, for example.
We were asked to make a little banner with some text that could be later closed with some iconography that would represent close/dismiss/go away.
One engineer suggested "Here's some text + ICON.PNG"
The next offered a more scalable option with "Here's some text + ICON.SVG"
Both are fine ideas that would work, while perhaps later having DPI or maintenance issues, but truly, perfectly cromulent ideas.
I have never been given this task, I am not a designer, and I am a mediocre front-end person. I asked what they wanted it to look like and they said "maybe a square with an X or a circle with an X or a circle with a line."
I offered, you know, there MUST be a Unicode Glyph for that. I mean, there's one for poop." Apparently I say poop in business meetings more than any other middle manager at the company, but that's fodder for another blog post.
We searched and lo and behold we found ☒ and ⊝ and simply added them to the end of the string. They scale visibly, require no downloads or extra dependencies, and can be colored and styled nicely because they are text.
One of the engineers said "how do you even know this crap?" I smiled and shrugged and we moved on to the doing.
To be clear, this post isn't self-congratulatory. Perhaps you had the same idea. This interaction was all of 10 minutes long. But I'm interested in the HOW did I know this? Note that I didn't actually KNOW that these glyphs existed. I knew only that they SHOULD exist. They MUST.
How many times have you been coding and said "You know, there really must be a function/site/tool that does x/y/z?" All the time, right? You don't know the answers but you know someone must have AND must have solved it in a specific way such that you could find it. A new developer doesn't have this intuition - this sense of technical smell - yet.
How is technical gut and intuition and smell developed? Certainly by doing, by osmosis, by time, by sleeping, and waking, and doing it again.
I think it's exposure. It's exposure to a diverse set of technical problems that all build on a solid base of fundamentals.
Rob and I are going to try to expand on how this technical confidence gets developed in The Imposter's Handbook: Season 2 as topics like Logic, Binary and Logical Circuits, Compression and Encoding, Encryption and Cryptanalysis, and Networking and Protocols are discussed. But I want to also understand how/if/when these topics and examples excite the reader...and most importantly do they provide the reader with that missing Tetris Piece of Knowledge that moves you from a journeyperson developer to someone who can more confidently wear the label Computer Science 9, Obscure Trivia 11.
What do you think? Sound off in the comments and help me and Rob understand!
Sponsor: Preview the latest JetBrains Rider with its built-in spell checking, initial Blazor support, partial C# 7.3 support, enhanced debugger, C# Interactive, and a redesigned Solution Explorer.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
If you've looked at csproj (C# (csharp) projects) in the past in a text editor you probably looked away quickly. They are effectively MSBuild files that orchestrate the build process. Phrased differently, a csproj file is an instance of an MSBuild file.
In Visual Studio 2017 and .NET Core 2 (and beyond) the csproj format is MUCH MUCH leaner. There's a lot of smart defaults, support for "globbing" like **/*.cs, etc and you don't need to state a bunch of obvious stuff. Truly you can take earlier msbuild/csproj files and get them down to a dozen lines of XML, plus package references. PackageReferences (references to NuGet packages) should be moved out of packages.config and into the csproj. This lets you manage all project dependencies in one place and gives you and uncluttered view of top-level dependencies.
However, upgrading isn't as simple as "open the old project file and have VS automatically migrate you."
You have some options when migrating to .NET Core and the .NET Standard.
Great a new project file with something like "dotnet new classlib" and then manually get your projects building from the top (most common ancestor) project down
Try to use an open source 3rd party migration tool
Damian on my team recommends option one - a fresh project - as you'll learn more and avoid bringing cruft over. I agree, until there's dozens of projects, then I recommend trying a migration tool AND then comparing it to a fresh project file to avoid adding cruft. Every project/solution is different, so expect to spend some time on this.
The best way to learn this might be by watching it happen for real. Wade from Salesforce was tasked with upgrading his 4+ year old .NET Framework (Windows) based SDK to portable and open source .NET Core. He had some experience building for older versions of Mono and was thoughtful about not calling Windows-specific APIs so he knows the code is portable. However he needs to migrate the project files and structure AND get the Unit Tests running with "dotnet test" and the command line.
I figured I'd give him a head start by actually doing part of the work. It's useful to do this because, frankly, things go wrong and it's not pretty!
I started with Hans van Bakel's excellent CsProjToVS2017 global tool. It does an excellent job of getting your project 85% of the way there. To be clear, don't assume anything and not every warning will apply to you. You WILL need to go over every line of your project files, but it is an extraordinarily useful tool. If you have .NET Core 2.1, install it globally like this:
Then its called (unfortunately) with another command "csproj-to-2017" and you can pass in a solution or an individual csproj.
After you've done the administrivia of the actual project conversion, you'll also want to make educated decisions about the 3rd party libraries you pull in. For example, if you want to make your project cross-platform BUT you depend on some library that is Windows only, why bother trying to port? Well, many of your favorite libraries DO have "netstandard" or ".NET Standard" versions. You'll see in the video below how I pull Wade's project's reference forward with a new version of JSON.NET and a new NuUnit. By the end we are building and the command line and running tests as well with code coverage.
Please head over to my YouTube and check it out. Note this happened live and spontaneously plus I had a YouTube audience giving me helpful comments, so I'll address them occasionally.
If you find things like this useful, let me know in the comments and maybe I'll do more of them. Also, do you think things like this belong on the Visual Studio Twitch Channel? Go follow my favs on Twitch CSharpFritz and Noopkat for more live coding fun!
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.