<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>Ookii.org</title>
    <link>http://www.ookii.org/blog</link>
    <description>Feed for the blog of Ookii.org.</description>
    <lastBuildDate>Wed, 09 Aug 2023 00:37:26 Z</lastBuildDate>
    <a10:id>https://www.ookii.org</a10:id>
    <a10:link rel="self" type="application/rss+xml" href="https://feeds.feedburner.com/ookii" />
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/dark_mode</guid>
      <link>https://www.ookii.org/Blog/dark_mode</link>
      <title>Dark mode</title>
      <description>&lt;p&gt;To modernize the appearance of my site a little bit, I've now added a dark mode!&lt;/p&gt;
&lt;p&gt;It's triggered by your device's color scheme, so if you've enabled a dark theme on your PC, smartphone, tablet, etch-a-sketch, or whatever you're using to browse this site, you should see the dark version of this site. There is currently no way to trigger it manually, so a browser that supports this is required (fortunately, that's pretty much all modern browsers).&lt;/p&gt;
&lt;p&gt;If you're not seeing it, you may have to force reload the stylesheet for this site. On PC, you can do this by pressing &lt;code&gt;CTRL-F5&lt;/code&gt; on this page. On mobile, the only way I'm aware of is to completely clear your browser's cache.&lt;/p&gt;
</description>
      <pubDate>Wed, 09 Aug 2023 00:37:26 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/ookiicommandline_40</guid>
      <link>https://www.ookii.org/Blog/ookiicommandline_40</link>
      <title>Ookii.CommandLine 4.0</title>
      <description>&lt;p&gt;When I released &lt;a href="https://www.ookii.org/Blog/ookiicommandline_30"&gt;version 3.0&lt;/a&gt; of Ookii.CommandLine last year, I wasn't
expecting to have another major release so soon. I still had some ideas for new features, like
supporting automatic aliases based on prefixes of command line arguments, but they'd only warrant
minor revisions.&lt;/p&gt;
&lt;p&gt;But then, I started reading about &lt;a href="https://learn.microsoft.com/dotnet/csharp/roslyn-sdk/source-generators-overview"&gt;.Net source generators&lt;/a&gt;,
and I thought, that's something I could use. Ookii.CommandLine used reflection to determine the
command line arguments at runtime, so a source generator would be able to do the same job at compile
time. That would make it faster, make the library trim-friendly, and allow me to emit warnings and
errors for unsupported argument settings, define positional arguments based on the order of the
class members rather than explicit numbers, and extract default values from property initializers.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.ookii.org/Link/CommandLineGitHub"&gt;Ookii.CommandLine 4.0&lt;/a&gt; now gives you that option. You can apply the
&lt;a href="https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm"&gt;&lt;code&gt;GeneratedParserAttribute&lt;/code&gt;&lt;/a&gt; to any class that defines command line arguments, and the parser will
be generated at compile time.&lt;/p&gt;
&lt;pre class="code"&gt;[&lt;span class="type"&gt;GeneratedParser&lt;/span&gt;]
&lt;span class="keyword"&gt;partial&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="type"&gt;MyArguments&lt;/span&gt;
{
    [&lt;span class="type"&gt;CommandLineArgument&lt;/span&gt;(IsPositional = &lt;span class="keyword"&gt;true&lt;/span&gt;)]
    [&lt;span class="type"&gt;Description&lt;/span&gt;(&lt;span class="string"&gt;&amp;quot;A required positional argument.&amp;quot;&lt;/span&gt;)]
    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;required&lt;/span&gt; &lt;span class="keyword"&gt;string&lt;/span&gt; Required { &lt;span class="keyword"&gt;get&lt;/span&gt;; &lt;span class="keyword"&gt;set&lt;/span&gt;; }

    [&lt;span class="type"&gt;CommandLineArgument&lt;/span&gt;(IsPositional = &lt;span class="keyword"&gt;true&lt;/span&gt;)]
    [&lt;span class="type"&gt;Description&lt;/span&gt;(&lt;span class="string"&gt;&amp;quot;An optional positional argument.&amp;quot;&lt;/span&gt;)]
    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;int&lt;/span&gt; Optional { &lt;span class="keyword"&gt;get&lt;/span&gt;; &lt;span class="keyword"&gt;set&lt;/span&gt;; } = 42;

    [&lt;span class="type"&gt;CommandLineArgument&lt;/span&gt;]
    [&lt;span class="type"&gt;Description&lt;/span&gt;(&lt;span class="string"&gt;&amp;quot;An argument that can only be supplied by name.&amp;quot;&lt;/span&gt;)]
    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="type"&gt;DateTime&lt;/span&gt; Named { &lt;span class="keyword"&gt;get&lt;/span&gt;; &lt;span class="keyword"&gt;set&lt;/span&gt;; }

    [&lt;span class="type"&gt;CommandLineArgument&lt;/span&gt;]
    [&lt;span class="type"&gt;Description&lt;/span&gt;(&lt;span class="string"&gt;&amp;quot;A switch argument, which doesn&amp;#39;t require a value.&amp;quot;&lt;/span&gt;)]
    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;bool&lt;/span&gt; Switch { &lt;span class="keyword"&gt;get&lt;/span&gt;; &lt;span class="keyword"&gt;set&lt;/span&gt;; }
}&lt;/pre&gt;
&lt;p&gt;That still might not have warranted a major version bump, if trimming hadn't been a goal.
Ookii.CommandLine relied on the &lt;a href="https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter"&gt;&lt;code&gt;TypeConverter&lt;/code&gt;&lt;/a&gt; class to convert argument values from strings to
their defined type, and I soon discovered that determining the right &lt;a href="https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter"&gt;&lt;code&gt;TypeConverter&lt;/code&gt;&lt;/a&gt; for any
type at compile time wasn't really possible, and calling &lt;a href="https://learn.microsoft.com/dotnet/api/system.componentmodel.typedescriptor.getconverter"&gt;&lt;code&gt;TypeDescriptor.GetConverter()&lt;/code&gt;&lt;/a&gt; at
runtime was inherently not compatible with trimming an application. I guess this is why
System.CommandLine limits their supported types to just a chosen handful, and everything else
requires custom converters.&lt;/p&gt;
&lt;p&gt;The solution was &lt;a href="https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm"&gt;&lt;code&gt;ArgumentConverter&lt;/code&gt;&lt;/a&gt;, a new way to convert argument values, that replaces
&lt;a href="https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter"&gt;&lt;code&gt;TypeConverter&lt;/code&gt;&lt;/a&gt;. Not only is it trim-friendly, it also enables converting arguments using
&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.readonlyspan-1"&gt;&lt;code&gt;ReadOnlySpan&amp;lt;char&amp;gt;&lt;/code&gt;&lt;/a&gt;, which means that the parser can avoid allocating strings when splitting
arguments provided like &lt;code&gt;-Name:value&lt;/code&gt;, another performance improvement.&lt;/p&gt;
&lt;p&gt;However, it does mean that any command line arguments classes that relied on a &lt;a href="https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter"&gt;&lt;code&gt;TypeConverter&lt;/code&gt;&lt;/a&gt;
will no longer function correctly without modifications, so the major version needed to change
again. While I was at it, I made a few other breaking changes, the biggest of which was probably the
removal of constructor parameters as a way to define command line arguments. Now, properties and
methods are your only choice. I was never a big fan of the way constructor parameters worked, and
how clumsy it was to add attributes to them. They were already de-emphasized in the documentation,
and were mostly left over from version 1.0 (sadly lost to time), where they were the only way to
define positional arguments. The work needed to support them with source generation meant they ended
up on the chopping block this time.&lt;/p&gt;
&lt;p&gt;For these and other breaking changes, check the &lt;a href="https://github.com/SvenGroot/Ookii.CommandLine/blob/main/docs/Migrating.md"&gt;migration guide&lt;/a&gt;.
As with version 3.0, most users probably won't need to make many changes.&lt;/p&gt;
&lt;p&gt;Besides source generation and new argument converters, Ookii.CommandLine 4.0 gives you plenty of
other goodness, including the aforementioned automatic prefix aliases, support for C# 11 &lt;code&gt;required&lt;/code&gt;
properties, a new attribute for value descriptions that allows for localization, canceling parsing
with success and access the remaining arguments afterwards, support for nested subcommands, source
link integration, and more.&lt;/p&gt;
&lt;p&gt;See the &lt;a href="https://www.ookii.org/Link/CommandLineVersionHistory"&gt;change log&lt;/a&gt; for everything that's new.&lt;/p&gt;
&lt;p&gt;Get it on &lt;a href="https://www.ookii.org/Link/NuGetCommandLine"&gt;NuGet&lt;/a&gt; or &lt;a href="https://www.ookii.org/Link/CommandLineGitHub"&gt;GitHub&lt;/a&gt;. You can also
&lt;a href="https://dotnetfiddle.net/fgLvSl"&gt;try out the parser&lt;/a&gt; or &lt;a href="https://dotnetfiddle.net/vGIG78"&gt;subcommands&lt;/a&gt;
on .Net Fiddle.&lt;/p&gt;
</description>
      <pubDate>Thu, 20 Jul 2023 20:07:08 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/jumbos_jobbuilder_part_2</guid>
      <link>https://www.ookii.org/Blog/jumbos_jobbuilder_part_2</link>
      <title>Jumbo's JobBuilder: part 2</title>
      <description>&lt;p&gt;This is fifth article about &lt;a href="https://www.ookii.org/Blog/Jumbo"&gt;Jumbo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.ookii.org/Blog/jumbos_jobbuilder_part_1"&gt;Previously&lt;/a&gt;, we looked at how Jumbo required you to create your own
job configurations, and how I tried to alleviate that with helpers for common job structures.&lt;/p&gt;
&lt;p&gt;I wanted a way to make this easier, and while I didn't want to use a separate language like Hadoop's
&lt;a href="https://pig.apache.org/"&gt;Pig&lt;/a&gt; did, I was inspired by its simple, imperative nature: you just specify
a sequence of operations, and it turns that into a sequence of MapReduce jobs.&lt;/p&gt;
&lt;p&gt;The JobBuilder would end up doing the same thing for Jumbo, but instead you used a sequence of
method calls in ordinary C# code. These would be 'compiled' into a job configuration.&lt;/p&gt;
&lt;p&gt;This had two key goals:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Let the JobBuilder make certain decisions about job structure, such as when to use a local
aggregation step, or how many tasks to use in a channel-input stage.&lt;/li&gt;
&lt;li&gt;Allow the user to create tasks using simple methods, instead of having to write a whole class.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first was accomplished by having the JobBuilder define helper functions for certain operations,
such as &lt;code&gt;AccumulateRecords&lt;/code&gt;, that knew how to create the kind of job structure appropriate for that
operation. That may not seem much better than the old &lt;code&gt;AccumulatorTask&lt;/code&gt; approach, but it had one
key advantage: you could now incorporate this operation in a larger job structure, rather than that
structure having to be your entire job.&lt;/p&gt;
&lt;p&gt;It also meant the JobBuilder could apply certain heuristics, such as: if there is only one input
task, you don't need a second aggregation stage; everything can be done locally. It's this kind of
logic that lets WordCount in the &lt;a href="https://github.com/SvenGroot/JumboCore/blob/main/doc/QuickStart/FirstJob.md"&gt;quick start guide&lt;/a&gt;
run in a single task if the input is small.&lt;/p&gt;
&lt;p&gt;The second goal had some complications. I didn't really want to give Jumbo Jet's
&lt;a href="https://www.ookii.org/docs/jumbo-2.0/html/T_Ookii_Jumbo_Jet_TaskExecutionUtility.htm"&gt;TaskExecutionUtility&lt;/a&gt; (the thing
responsible for most of the logic of how to run tasks) the ability to invoke arbitrary functions. It
would break too much of the logic of how tasks were constructed, and would require a lot of bespoke
code for different types of task methods. No, I wanted to stick to &lt;a href="https://www.ookii.org/docs/jumbo-2.0/html/T_Ookii_Jumbo_Jet_ITask_2.htm"&gt;ITask&amp;lt;TInput, TOutput&amp;gt;&lt;/a&gt;
at that level, which meant JobBuilder had to translate between the two.&lt;/p&gt;
&lt;p&gt;That meant dynamically generating MSIL to create task classes that invoked the methods the user
specified. These classes would be generated at job submission time, saved to a generated assembly,
which the &lt;code&gt;TaskExecutionUtility&lt;/code&gt; could just load like normal task type without being aware of any of
this. It was a fun challenge to figure out how to do this, and it made it much easier to create
custom tasks.&lt;/p&gt;
&lt;p&gt;The only limitation is that, unfortunately, if the method you used for your task was not &lt;code&gt;public static&lt;/code&gt;,
I couldn't emit a direct call in the generated MSIL. In that case, I had to serialize the delegate
you pass in, and during task execution, deserialize it and call the method through that, which is
much slower. Unfortunately, this meant that using lambdas (which never generate a public method) was
possible, but not ideal.&lt;/p&gt;
&lt;p&gt;The first iteration of the JobBuilder meant you could now write code like this to create a job:&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span class="keyword"&gt;var&lt;/span&gt; builder = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;JobBuilder&lt;/span&gt;();
&lt;span class="keyword"&gt;var&lt;/span&gt; input = builder.CreateRecordReader&amp;lt;&lt;span class="type"&gt;Utf8StringWritable&lt;/span&gt;&amp;gt;(_inputPath, &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;LineRecordReader&lt;/span&gt;));
&lt;span class="keyword"&gt;var&lt;/span&gt; collector = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;RecordCollector&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;KeyValuePairWritable&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;Utf8StringWritable&lt;/span&gt;, &lt;span class="type"&gt;Int32Writable&lt;/span&gt;&amp;gt;&amp;gt;(&lt;span class="keyword"&gt;null&lt;/span&gt;, &lt;span class="keyword"&gt;null&lt;/span&gt;, _combinerTasks == 0 ? &lt;span class="keyword"&gt;null&lt;/span&gt; : (&lt;span class="keyword"&gt;int&lt;/span&gt;?)_combinerTasks);
builder.ProcessRecords(input, collector.CreateRecordWriter(), WordCount);

&lt;span class="keyword"&gt;var&lt;/span&gt; output = builder.CreateRecordWriter&amp;lt;&lt;span class="type"&gt;KeyValuePairWritable&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;Utf8StringWritable&lt;/span&gt;, &lt;span class="type"&gt;Int32Writable&lt;/span&gt;&amp;gt;&amp;gt;(_outputPath,
    &lt;span class="keyword"&gt;typeof&lt;/span&gt;(TextRecordWriter&amp;lt;&lt;span class="type"&gt;KeyValuePairWritable&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;Utf8StringWritable&lt;/span&gt;, &lt;span class="type"&gt;Int32Writable&lt;/span&gt;&amp;gt;&amp;gt;), BlockSize, ReplicationFactor);

builder.AccumulateRecords(collector.CreateRecordReader(), output, WordCountAccumulator);

...

[AllowRecordReuse]
&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; WordCount(&lt;span class="type"&gt;RecordReader&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;Utf8StringWritable&lt;/span&gt;&amp;gt; input, RecordWriter&amp;lt;&lt;span class="type"&gt;KeyValuePairWritable&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;Utf8StringWritable&lt;/span&gt;, &lt;span class="type"&gt;Int32Writable&lt;/span&gt;&amp;gt;&amp;gt; output) { ... }

[AllowRecordReuse]
&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; WordCountAccumulator(&lt;span class="type"&gt;Utf8StringWritable&lt;/span&gt; key, &lt;span class="type"&gt;Int32Writable&lt;/span&gt; &lt;span class="keyword"&gt;value&lt;/span&gt;, &lt;span class="type"&gt;Int32Writable&lt;/span&gt; newValue) { ... }&lt;/pre&gt;
&lt;p&gt;That was better: no more task classes, no more explicitly creating child stages, and the job was
defined by basically two function calls: &lt;code&gt;ProcessRecords&lt;/code&gt;, and &lt;code&gt;AccumulateRecords&lt;/code&gt;. The JobBuilder
also kept track of all the assemblies used by each record reader, writer, partitioner, and task you
used, and made sure they, and all their non-framework dependencies, would be uploaded to the DFS.&lt;/p&gt;
&lt;p&gt;There were two things I didn't like about this, though: all the explicit generic type parameters,
and the fact that you have to define your input, output, and channels (through the &lt;code&gt;RecordCollector&lt;/code&gt;
class) explicitly before calling processing functions. It kind of meant you had to think about your
stages backwards, because you had to define their output before you invoked their processing
functions.&lt;/p&gt;
&lt;p&gt;I wasn't quite satisfied with this. It still wasn't as easy as I wanted it to be. I tried to improve things
by just adding even more specific helper methods, so you could write WordCount like this:&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span class="keyword"&gt;var&lt;/span&gt; input = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;DfsInput&lt;/span&gt;(_inputPath, &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;WordRecordReader&lt;/span&gt;));
&lt;span class="keyword"&gt;var&lt;/span&gt; output = CreateDfsOutput(_outputPath, &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;TextRecordWriter&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;Pair&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;Utf8String&lt;/span&gt;, &lt;span class="keyword"&gt;int&lt;/span&gt;&amp;gt;&amp;gt;));

&lt;span class="type"&gt;StageBuilder&lt;/span&gt;[] stages = builder.Count&amp;lt;&lt;span class="type"&gt;Utf8String&lt;/span&gt;&amp;gt;(input, output);

((Channel)stages[0].Output).PartitionCount = _combinerTasks;
stages[0].StageId = &lt;span class="string"&gt;&amp;quot;WordCountStage&amp;quot;&lt;/span&gt;;
stages[1].StageId = &lt;span class="string"&gt;&amp;quot;WordCountSumStage&amp;quot;&lt;/span&gt;;&lt;/pre&gt;
&lt;p&gt;Which was kind of cheating (since I hadn't solved the problem, I just hid it), and the way you
customized things like the stage IDs and task count wasn't exactly great.&lt;/p&gt;
&lt;p&gt;Eventually, I had an idea that completely changed how the job builder worked. Instead of having
to define channels and outputs up front, each operation could directly serve as the input of the
next operation, and if that created a channel, the resulting object could be used to customize that
channel as well as the stage itself.&lt;/p&gt;
&lt;p&gt;This new JobBuilder still had helpers for specific types of operations that could use custom logic
(such as adding the local aggregation step), as well as helpers for custom channel types (sorting),
and for complex operations like joins. And of course, you could still use custom task classes or
manually define child stages if none of the helpers sufficed, combining them seamlessly with the
helpers.&lt;/p&gt;
&lt;p&gt;Finally, you could specify every step in a straight-forward way:&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span class="keyword"&gt;var&lt;/span&gt; job = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;JobBuilder&lt;/span&gt;();
&lt;span class="keyword"&gt;var&lt;/span&gt; input = job.Read(InputPath, &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;LineRecordReader&lt;/span&gt;));
&lt;span class="keyword"&gt;var&lt;/span&gt; words = job.Map&amp;lt;&lt;span class="type"&gt;Utf8String&lt;/span&gt;, &lt;span class="type"&gt;Pair&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;Utf8String&lt;/span&gt;, &lt;span class="keyword"&gt;int&lt;/span&gt;&amp;gt;&amp;gt;(input, MapWords);
&lt;span class="keyword"&gt;var&lt;/span&gt; aggregated = job.GroupAggregate&amp;lt;&lt;span class="type"&gt;Utf8String&lt;/span&gt;, &lt;span class="keyword"&gt;int&lt;/span&gt;&amp;gt;(words, AggregateCounts);
job.Write(aggregated, OutputPath, &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;TextRecordWriter&lt;/span&gt;&amp;lt;&amp;gt;));&lt;/pre&gt;
&lt;p&gt;This was the kind of simplicity I wanted. There were no more specific job helpers, and every step
was explicit while still being easy to use. Even emulating MapReduce was now simple:&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span class="keyword"&gt;var&lt;/span&gt; job = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;JobBuilder&lt;/span&gt;();
&lt;span class="keyword"&gt;var&lt;/span&gt; input = job.Read(InputPath, &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;LineRecordReader&lt;/span&gt;));
&lt;span class="keyword"&gt;var&lt;/span&gt; mapped = job.Map&amp;lt;&lt;span class="type"&gt;Utf8String&lt;/span&gt;, &lt;span class="type"&gt;Pair&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;Utf8String&lt;/span&gt;, &lt;span class="keyword"&gt;int&lt;/span&gt;&amp;gt;&amp;gt;(input, MapWords);
&lt;span class="keyword"&gt;var&lt;/span&gt; sorted = job.SpillSortCombine&amp;lt;&lt;span class="type"&gt;Utf8String&lt;/span&gt;, &lt;span class="keyword"&gt;int&lt;/span&gt;&amp;gt;(mapped, ReduceWordCount);
&lt;span class="keyword"&gt;var&lt;/span&gt; reduced = job.Reduce&amp;lt;&lt;span class="type"&gt;Utf8String&lt;/span&gt;, &lt;span class="keyword"&gt;int&lt;/span&gt;, &lt;span class="type"&gt;Pair&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;Utf8String&lt;/span&gt;, &lt;span class="keyword"&gt;int&lt;/span&gt;&amp;gt;&amp;gt;(sorted, ReduceWordCount);
job.Write(reduced, OutputPath, &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;TextRecordWriter&lt;/span&gt;&amp;lt;&amp;gt;));&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;SpillSortCombine&lt;/code&gt; configures the channel in between the map and reduce, without the user
needing to know that's what it does. And despite it being an operation that has no stages of its
own, you can just apply it directly to DFS input and output; the JobBuilder makes sure a valid job
structure is created.&lt;/p&gt;
&lt;p&gt;The only limitation I could never get away from is the need for explicit generic type arguments.
I was able to make things a bit easier; everywhere you specify a type directly, whether record
readers, writers, partitioners, or even tasks (if not using methods), you can specify the &amp;quot;open&amp;quot;
generic type (like &lt;code&gt;typeof(TextRecordWriter&amp;lt;&amp;gt;)&lt;/code&gt;) above, and the JobBuilder will instantiate it with
the correct type for the records.&lt;/p&gt;
&lt;p&gt;However, for the actual processing methods, where they use delegates, it was always necessary to
specify both the input and output types explicitly, because C# isn't able to determine the type
arguments from the arguments of a delegate target (only from the return type). LINQ doesn't suffer
from this problem because the type of the input items is known, because you pass in an
&lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; or &lt;code&gt;IQueryable&amp;lt;T&amp;gt;&lt;/code&gt;. Whereas the types of &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;words&lt;/code&gt;, etc. above (all
implementing &lt;code&gt;IStageInput&lt;/code&gt;) are not generic, so can't be used to deduce the type.&lt;/p&gt;
&lt;p&gt;The only improvement I made was shortening a bunch of type names. &lt;code&gt;Utf8StringWritable&lt;/code&gt; became
&lt;code&gt;Utf8String&lt;/code&gt;, &lt;code&gt;KeyValuePairWritable&lt;/code&gt; became &lt;code&gt;Pair&lt;/code&gt;, and &lt;code&gt;Int32Writable&lt;/code&gt; (and a bunch of similar
wrappers) went away in favor of being able to directly use types like &lt;code&gt;int&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, why not make &lt;code&gt;IStageInput&lt;/code&gt; generic? Now the delegates work, but you have to specify explicit
types in other places, such as &lt;code&gt;Read&lt;/code&gt; above, and when calling &lt;code&gt;Process&lt;/code&gt; with a custom task class.
There is no way to derive the record type for &lt;code&gt;job.Read(InputPath, typeof(LineRecordReader))&lt;/code&gt;. I
thought of using a constraint like this:&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; IStageInput&amp;lt;TRecord&amp;gt; Read&amp;lt;TRecordReader, TRecord&amp;gt;(&lt;span class="keyword"&gt;string&lt;/span&gt; inputPath)
    &lt;span class="keyword"&gt;where&lt;/span&gt; TRecordReader : RecordReader&amp;lt;TRecord&amp;gt;&lt;/pre&gt;
&lt;p&gt;But, you still have to explicitly specify both generic arguments in that case.&lt;/p&gt;
&lt;p&gt;Maybe I could do better now, if I gave this some more thought. I can't immediately think of anything
that wouldn't have equal drawbacks, though. And, since I'm not developing Jumbo anymore, this is how
it will stay.&lt;/p&gt;
&lt;p&gt;Still, despite that little annoyance, I accomplished most of what I wanted with the final version of
the JobBuilder. It feels like a pretty natural way of defining jobs, and even if you want to
customize settings for stages and channels (for example, see
&lt;a href="https://github.com/SvenGroot/JumboCore/blob/main/doc/UserGuide/Tutorial2.md"&gt;advanced WordCount&lt;/a&gt;),
it works pretty well&lt;/p&gt;
&lt;p&gt;It's probably my favorite aspect of Jumbo, something that's really different from anything in Hadoop
(at least at the time), and I'm pretty proud of it. And, before I released Jumbo, nobody really knew
about it. Since it had no real value to my research, I never published about it. It was only
mentioned on one page in my dissertation, but that was it.&lt;/p&gt;
&lt;p&gt;That's why I wanted to release Jumbo. It's why I wanted to write these articles about it. Even if
nobody cares, the work I did on this is at least preserved now. And that's worth something, even if
only to me.&lt;/p&gt;
</description>
      <pubDate>Tue, 07 Feb 2023 18:54:34 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/jumbos_jobbuilder_part_1</guid>
      <link>https://www.ookii.org/Blog/jumbos_jobbuilder_part_1</link>
      <title>Jumbo's JobBuilder: part 1</title>
      <description>&lt;p&gt;This is fourth article about &lt;a href="https://www.ookii.org/Blog/Jumbo"&gt;Jumbo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From the moment I started working on Jumbo, I knew I wanted two things: I wanted it to be more
flexible than plain MapReduce, and I still wanted it to be easy to use.&lt;/p&gt;
&lt;p&gt;While I didn't want to be limited to MapReduce, I also didn't just want to allow arbitrary task
graphs, because I felt that was too complex for what I wanted to do. By abstracting MapReduce into a
linear sequence of &amp;quot;stages,&amp;quot; of which you could have an arbitrary number, I feel like I struck a
good balance between allowing alternative job structures, while still not getting too complicated.&lt;/p&gt;
&lt;p&gt;Add to that things like making channel operations (such as sorting) optional, and suddenly you could
use hash-table aggregation, you could do in one job things that in MapReduce would've required several
jobs, and by allowing multiple input stages, you could do things that were traditionally hard to
emulate in MapReduce, such as joins.&lt;/p&gt;
&lt;p&gt;Still, this added flexibility did come with some complexity. In Hadoop, you write a map function,
a reduce function, set up a simple configuration specifying what they are, and you're done. With
Jumbo, you had to put a little more thought into what kind of structure is right for your job, and
creating a JobConfiguration was a bit more complex.&lt;/p&gt;
&lt;p&gt;From the earliest versions of Jumbo Jet I wrote, the job configuration was always an XML file, and
it was always a serialization of the &lt;a href="https://www.ookii.org/docs/jumbo-2.0/html/T_Ookii_Jumbo_Jet_Jobs_JobConfiguration.htm"&gt;&lt;code&gt;JobConfiguration&lt;/code&gt;&lt;/a&gt;
class. However, the structure of that format changed quite a bit. Originally, you needed to add each
task individually, which meant that if you had an input file with a 1000 blocks, &lt;a href="https://github.com/SvenGroot/Jumbo/blob/1dab4bdbd4310f7907ea04205027b06dc6e0e933/ClientSample/Program.cs#L93"&gt;you needed to add
a 1000 &lt;code&gt;TaskConfiguration&lt;/code&gt; elements to the configuration&lt;/a&gt;.
Not exactly scalable.&lt;/p&gt;
&lt;p&gt;Eventually, I switched this to having a &lt;code&gt;StageConfiguration&lt;/code&gt; instead, and letting the JobServer
figure things out from there. But, you still needed to essentially build the job structure manually.
At first, this still meant manually creating each stage and channel. Later, I added helpers, which
meant that creating WordCount's configuration would look something like this:&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span class="keyword"&gt;var&lt;/span&gt; config = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;JobConfiguration&lt;/span&gt;(&lt;span class="keyword"&gt;new&lt;/span&gt;[] { &lt;span class="type"&gt;Assembly&lt;/span&gt;.GetExecutingAssembly() });
&lt;span class="keyword"&gt;var&lt;/span&gt; input = dfsClient.NameServer.GetFileSystemEntryInfo(&lt;span class="string"&gt;&amp;quot;/input&amp;quot;&lt;/span&gt;);
&lt;span class="keyword"&gt;var&lt;/span&gt; firstStage = config.AddInputStage(&lt;span class="string"&gt;&amp;quot;WordCount&amp;quot;&lt;/span&gt;, input, &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;WordCountTask&lt;/span&gt;), &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;LineRecordReader&lt;/span&gt;));
&lt;span class="keyword"&gt;var&lt;/span&gt; localAggregation = config.AddPointToPointStage(&lt;span class="string"&gt;&amp;quot;LocalAggregation&amp;quot;&lt;/span&gt;, firstStage, &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;WordCountAggregationTask&lt;/span&gt;), ChannelType.Pipeline, &lt;span class="keyword"&gt;null&lt;/span&gt;, &lt;span class="keyword"&gt;null&lt;/span&gt;);
&lt;span class="keyword"&gt;var&lt;/span&gt; info = &lt;span class="keyword"&gt;new&lt;/span&gt; InputStageInfo(localAggregation)
{
    ChannelType = ChannelType.File,
    PartitionerType = &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;HashPartition&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;Pair&lt;/span&gt;&amp;lt;&lt;span class="type"&gt;Utf8String&lt;/span&gt;, &lt;span class="keyword"&gt;int&lt;/span&gt;&amp;gt;&amp;gt;);
};

config.AddStage(&lt;span class="string"&gt;&amp;quot;WordCountAggregation&amp;quot;&lt;/span&gt;, localAggregation, &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;WordCountAggregationTask&lt;/span&gt;), taskCount, info, &lt;span class="string"&gt;&amp;quot;/wcoutput&amp;quot;&lt;/span&gt;, &lt;span class="keyword"&gt;typeof&lt;/span&gt;(&lt;span class="type"&gt;TextRecordWriter&lt;/span&gt;));
&lt;span class="keyword"&gt;var&lt;/span&gt; job = jetClient.JobServer.CreateJob();
jetClient.RunJob(job, config, dfsClient, &lt;span class="keyword"&gt;new&lt;/span&gt;[] { &lt;span class="type"&gt;Assembly&lt;/span&gt;.GetExecutingAssembly().Location });&lt;/pre&gt;
&lt;p&gt;Maybe it's not terrible, but it's not super friendly (and this was already better than it started
out at). I tried to improve things by creating helpers for specific job types; a job like WordCount
was an &lt;code&gt;AccumulatorJob&lt;/code&gt;; a two-stage job with optional sorting (like MapReduce) was a &lt;code&gt;BasicJob&lt;/code&gt;.
This worked, but kind of left you hanging if you wanted a different job structure.&lt;/p&gt;
&lt;p&gt;Some things got easier quickly; instead of manually using the &lt;code&gt;JetClient&lt;/code&gt; class, I introduced the
concept of a job runner, a special class use by JetShell. But defining the structure of the job
stayed like this, unless you could use a helper for a predefined job structure, for a long time.&lt;/p&gt;
&lt;p&gt;One of my first ideas for how to make creating jobs easier was to adapt LINQ (which was pretty new
at the time). I even thought that something like that could have potential for publication.
Unfortunately, Microsoft itself beat me to the punch by publishing a paper on
&lt;a href="https://www.microsoft.com/research/project/dryadlinq/"&gt;DryadLINQ&lt;/a&gt;, so that was no longer an
option.&lt;/p&gt;
&lt;p&gt;After that, I no longer saw this as something I could publish a paper about, but just for my own
gratification I still wanted a better way to create jobs.&lt;/p&gt;
&lt;p&gt;I thought about alternative approaches. I could still do LINQ, but it would be complicated, and
without a research purpose I didn't want to invest that kind of time. Hadoop had its own methods;
while single jobs were easy, complex processing that required multiple jobs or joins was still hard
even there, and the best solution at the time was &lt;a href="https://pig.apache.org/"&gt;Pig&lt;/a&gt;, which used a
custom programming language, Pig Latin, for creating jobs. I didn't much like that either, since
you'd often have to combine both Pig Latin and Java, and that didn't seem like a good option to me.&lt;/p&gt;
&lt;p&gt;No, I wanted something that kept you in C#, and the solution I came up with was the JobBuilder,
which we'll discuss next time.&lt;/p&gt;
</description>
      <pubDate>Thu, 26 Jan 2023 00:13:38 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/ookiicommandline_30</guid>
      <link>https://www.ookii.org/Blog/ookiicommandline_30</link>
      <title>Ookii.CommandLine 3.0</title>
      <description>&lt;p&gt;When I released &lt;a href="https://www.ookii.org/Software/CommandLineParser/Cpp"&gt;Ookii.CommandLine for C++&lt;/a&gt;, I realized I had quite a
backlog of things I wanted to update in Ookii.CommandLine, not just for the C++ version, but for the .Net version
as well.&lt;/p&gt;
&lt;p&gt;The result is the release of &lt;a href="https://www.ookii.org/Link/CommandLineGitHub"&gt;Ookii.CommandLine 3.0 for .Net&lt;/a&gt;. This is the biggest release
of Ookii.CommandLine yet, with many new features, including support for an additional, more POSIX-like argument
syntax, argument validation and dependencies, automatic name transformations, an updated subcommand API,
usage help color output, more powerful customization, and more.&lt;/p&gt;
&lt;p&gt;Seriously, &lt;a href="https://www.ookii.org/Link/CommandLineVersionHistory"&gt;there's a lot&lt;/a&gt;. Even version 2.0, which was a substantial rewrite from
the original, wasn't anywhere near as big. Unfortunately, that does mean there's some breaking changes, but I
expect most users won't need to make too many changes.&lt;/p&gt;
&lt;p&gt;One question you might have is, when is all this new stuff coming to the C++ version? Unfortunately, I don't have
a good answer. I'll probably add at least some of the new features to the C++ version, but it probably won't be
all at once, and I'm not going to give a timeline either. If there's any feature you want in particular, you should
&lt;a href="https://github.com/SvenGroot/Ookii.CommandLine.Cpp/issues"&gt;file an issue for it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Get it on &lt;a href="https://www.ookii.org/Link/NuGetCommandLine"&gt;NuGet&lt;/a&gt; or &lt;a href="https://www.ookii.org/Link/CommandLineGitHub"&gt;GitHub&lt;/a&gt;. You can also
&lt;a href="https://dotnetfiddle.net/fgLvSl"&gt;try it out on .Net Fiddle&lt;/a&gt;, or &lt;a href="https://dotnetfiddle.net/vGIG78"&gt;try out subcommands&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Thu, 01 Dec 2022 22:28:09 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/jumbo_and_net_remoting</guid>
      <link>https://www.ookii.org/Blog/jumbo_and_net_remoting</link>
      <title>Jumbo and .Net Remoting</title>
      <description>&lt;p&gt;This is the third article about &lt;a href="https://www.ookii.org/Blog/Jumbo"&gt;Jumbo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One of the things I knew I would need for Jumbo was a way for clients and servers to communicate. Clients
would have to communicate with the servers, but servers also had to communicate with each other,
mostly through heartbeats.&lt;/p&gt;
&lt;p&gt;In some cases, writing a custom TCP server and protocol made sense, like client communication with
the data server where large amounts of data had to be processed. But for most things, it was just a
matter of invoking some function on the target server, like when creating a file on the NameServer
or submitting a job to the JobServer. I figured it would be overkill to create completely custom
protocols for this. In fact, being able to easily do this was one of the reasons I picked .Net.&lt;/p&gt;
&lt;p&gt;WCF existed at the time, but I wasn't very familiar with it, and I'm not sure if Mono supported it
back then. So, I decided to use good, old-fashioned .Net Remoting. All I had to do was make some interfaces,
like &lt;a href="https://www.ookii.org/docs/jumbo-2.0/html/T_Ookii_Jumbo_Dfs_INameServerClientProtocol.htm"&gt;&lt;code&gt;INameServerClientProtocol&lt;/code&gt;&lt;/a&gt;,
inherit from &lt;code&gt;MarshalByRefObject&lt;/code&gt;, set up some minimal configuration, and I was in business. This
seemed to work great, both on .Net and Mono.&lt;/p&gt;
&lt;p&gt;All was well, until I started scaling up on Linux clusters. Suddenly, I was faced with extremely long
delays in remoting calls. This was particularly noticeable with heartbeats, which sometimes ended up taking
tens of seconds to complete. Now, I wasn't running thousands of nodes, where scaling issues might
be expected; I was running maybe forty at the most. A remoting call that takes a few milliseconds
to complete shouldn't suddenly take 10+ seconds in that environment.&lt;/p&gt;
&lt;p&gt;It took a lot of digging, but I eventually found the cause: it was a bug in Mono. Mono's remoting TCP server
channel implementation used a thread pool to dispatch requests. Now, the .Net BCL has a built-in thread pool class,
but remoting didn't use that. It used its own &lt;code&gt;RemotingThreadPool&lt;/code&gt;, which was used nowhere else.&lt;/p&gt;
&lt;p&gt;This thread pool had a fatal flaw. Once it reached a specific number of active threads, on the next
request it would wait 500ms for an existing thread to become available, before creating a new one.&lt;/p&gt;
&lt;p&gt;That's already not great. Worse, it did this &lt;em&gt;synchronously on the thread that accepts new connections!&lt;/em&gt;
Which meant that these delays would stack! If a connection attempt comes in, and one is already
waiting, it can't accept this connection until the waiting one is done. And if that one then also
hits the limit...&lt;/p&gt;
&lt;p&gt;Basically, if you get 10 new connection attempts when the pool is already at the limit, the last one
will end up waiting 5 seconds, instead of 500ms. This was the cause of my scalability problems, with
a few dozen nodes all trying to send heartbeats to the single NameServer and JobServer.&lt;/p&gt;
&lt;p&gt;I reported this bug, but it never got fixed. In fact, this problem &lt;a href="https://github.com/mono/mono/blob/b40e9939a7d07b30a75625692874f02bcc9be18f/mcs/class/System.Runtime.Remoting/System.Runtime.Remoting.Channels/RemotingThreadPool.cs#L104"&gt;is still in the code today&lt;/a&gt;,
at least as of this writing.&lt;/p&gt;
&lt;p&gt;I created my own patched version of Mono, which worked around the issue (I think I just removed the delay).
But, needing a custom-built Mono wasn't great for the usability of the project. I eventually ended up writing my
own RPC implementation, using Begin/End asynchronous methods, which performed better. Still, I refused
to merge this into the main branch (trunk, since this was SVN), still waiting for a Mono fix that would
never come.&lt;/p&gt;
&lt;p&gt;Eventually, I did switch to using the custom RPC implementation permanently, because if I ever did want to
release Jumbo, requiring users to patch and compile Mono wasn't really an option. And, it was probably
for the better, since .Net Core no longer has remoting, so I would've needed a different solution
anyway when making the port.&lt;/p&gt;
&lt;p&gt;And yes, just like .Net Remoting, my custom RPC mechanism depends on BinaryFormatter, because it
works much the same. And &lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide"&gt;BinaryFormatter is deprecated and insecure&lt;/a&gt;.
Since I have no interest in further developing Jumbo, that will remain that way, so do consider that
if you run Jumbo on any of your machines.&lt;/p&gt;
</description>
      <pubDate>Thu, 24 Nov 2022 01:11:37 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/ookiicommandline_for_cpp</guid>
      <link>https://www.ookii.org/Blog/ookiicommandline_for_cpp</link>
      <title>Ookii.CommandLine for C++</title>
      <description>&lt;p&gt;Today I'm releasing a library that I've had for a long time, but never released: &lt;a href="https://www.ookii.org/Software/CommandLineParser/Cpp"&gt;Ookii.CommandLine is now available for C++&lt;/a&gt;. It offers the same functionality as its .Net counterpart, but with an API that's suitable for C++.&lt;/p&gt;
&lt;p&gt;I've had this for a while, actually, but it was interwoven with another project that contains a lot of pieces that aren't really meant for release. I recently updated it to match Ookii.CommandLine 2.4 functionality, and figured, why not go the extra mile and move it into its own repository.&lt;/p&gt;
&lt;p&gt;So I did, and that repository is now available on &lt;a href="https://www.ookii.org/Link/CommandLineCppGitHub"&gt;GitHub&lt;/a&gt;. The library is header only, so it can be easily included in any project, as long as you use a compiler that supports C++20.&lt;/p&gt;
</description>
      <pubDate>Sun, 23 Oct 2022 23:33:37 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/a_tale_of_two_jumbos</guid>
      <link>https://www.ookii.org/Blog/a_tale_of_two_jumbos</link>
      <title>A tale of two Jumbos</title>
      <description>&lt;p&gt;This is the second article about &lt;a href="https://www.ookii.org/Blog/Jumbo#additional-posts-about-jumbo"&gt;Jumbo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As I said &lt;a href="https://www.ookii.org/Blog/jumbo_and_netmono"&gt;previously&lt;/a&gt;, Jumbo originally ran on Mono and the .Net Framework. Part of my hesitation
in releasing this project was because of how complicated it could be to get it running on that
environment.&lt;/p&gt;
&lt;p&gt;When &lt;a href="https://github.com/dotnet"&gt;.Net Core&lt;/a&gt; started getting more mature with the release of .Net Core 3, I was interested in
learning more about it. While I don't use .Net in my day-to-day anymore, I still like it, and
.Net Core's promise of cross-platform .Net would've saved me so much hassle back in the day.
So, I was curious to see whether I could get Jumbo running on it, just for fun.&lt;/p&gt;
&lt;p&gt;Porting was, fortunately, not that difficult. To make things easier on myself, I started in a clean workspace,
and copied over Jumbo's components one at a time, created SDK-style projects for them, and fixing
build errors until they compiled. Some changes were necessary, but it wasn't too bad.&lt;/p&gt;
&lt;h3 id="changes-made-for.net-core"&gt;Changes made for .Net Core&lt;/h3&gt;
&lt;p&gt;One early difference I ran into was how sockets worked. On Linux, if you listen on an IPv6 address,
you automatically also listen on the corresponding IPv4 address. If you subsequently also tried
to bind to the IPv4 address, it would fail. Jumbo handled this by only listening on IPv6 on Linux,
but on both IPv6 and IPv4 on Windows. This worked great for Mono.&lt;/p&gt;
&lt;p&gt;But, I guess .Net Core handled this differently, since it no longer listens on IPv4 automatically
on Linux, so I had to change how Jumbo behaved. Fortunately, that only meant changing the default
for the associated setting in Jumbo's configuration files.&lt;/p&gt;
&lt;p&gt;One of the biggest issues I ran into was the lack of the &lt;code&gt;AssemblyBuilder.Save&lt;/code&gt; method. This method,
needed to save dynamically generated assemblies to disk (which Jumbo's &lt;code&gt;JobBuilder&lt;/code&gt; depends on),
doesn't exist in .Net Core (and still doesn't in .Net 6). Fortunately, I was able to find a NuGet
package that provided the same functionality, &lt;a href="https://github.com/Lokad/ILPack"&gt;Lokad.ILPack&lt;/a&gt;,
which saved the day. Unfortunately, it had &lt;a href="https://github.com/Lokad/ILPack/issues/107"&gt;a bug&lt;/a&gt;
that prevented it from working for my scenario, but I was able to find and contribute a fix.&lt;/p&gt;
&lt;p&gt;Another minor issue was the lack of AppDomains. Jumbo's TaskServer normally runs tasks in an external
process, but it could run tasks in an AppDomain if requested, which was used to ease debugging and testing. Since AppDomains don't exist
anymore, I had to cut this feature. But, since it isn't used when running Jumbo normally, it wasn't
a big loss. The biggest difference is that it does make running the unit tests a bit more fragile, since
they now depend on the external TaskHost process.&lt;/p&gt;
&lt;p&gt;Then, I discovered that .Net Core can't serialize delegates, but I was &lt;a href="https://github.com/SvenGroot/JumboCore/blob/570dad6f9075456129a1d48e3f1e3a1db1ccbe2f/src/Ookii.Jumbo.Jet/Jobs/Builder/DynamicTaskBuilder.cs#L183"&gt;able to work around that too&lt;/a&gt;.
With that, I finally got all the tests
working. Not only that; I could run them on Linux now, which was never possible before (old Jumbo's
tests depended on NUnit and only ran on Windows).&lt;/p&gt;
&lt;p&gt;Another interesting part was the CRC library. Jumbo's DFS calculates checksums a lot, so original
Jumbo used a native library, for a faster implementation
than the managed version. That library was built as part of Jumbo, either with Visual C++ (on Windows),
or g++ (on Linux). Integrating that native build into the .Net Core build process turned out to be
a hassle.&lt;/p&gt;
&lt;p&gt;I did some speed tests to find out if this would still matter, and tried to optimize the managed fallback
algorithm a bit more. And then, I found &lt;a href="https://github.com/force-net/Crc32.NET"&gt;a CRC32 library for .Net Core&lt;/a&gt;
that was even faster than my original native implementation, so that solved that problem.&lt;/p&gt;
&lt;p&gt;Oh, and &lt;a href="https://www.ookii.org/Software/CommandLineParser"&gt;Ookii.CommandLine 2.3's&lt;/a&gt; inclusion of a .Net Standard 2.0 binary? That's
because I needed it for this Jumbo port! Otherwise, I'm not sure when I would've gotten around
to that.&lt;/p&gt;
&lt;p&gt;Finally, I just had to remove some of the Mono-specific parts from the code (there are probably still
some leftovers), and it was good to go. Except...&lt;/p&gt;
&lt;h3 id="dfsweb-and-jetweb"&gt;DfsWeb and JetWeb&lt;/h3&gt;
&lt;p&gt;The least straight-forward part of the port was, without a doubt, the two administration websites, &lt;a href="https://github.com/SvenGroot/JumboCore/blob/main/doc/UserGuide/DfsFeatures.md#dfsweb"&gt;DfsWeb&lt;/a&gt;
and &lt;a href="https://github.com/SvenGroot/JumboCore/blob/main/doc/UserGuide/JetFeatures.md#jetweb"&gt;JetWeb&lt;/a&gt;. These were
written using ASP.NET Web Forms (with code-behind files and everything), which just wasn't supported
in .Net Core. Not wanting to give up now, I persevered, and rewrote both projects to use ASP.NET Core,
replacing the Web Forms with Razor pages. Fortunately, neither site was particularly big, so there
were only a few pages to convert.&lt;/p&gt;
&lt;p&gt;This also had a big advantage: to use the websites, previously you needed IIS Express (on Windows)
or Mono XSP. Now, they could just run using Kestrel, the web server included with ASP.NET Core.&lt;/p&gt;
&lt;h3 id="scripts"&gt;Scripts&lt;/h3&gt;
&lt;p&gt;Jumbo was meant to run on a Linux cluster, and used a bunch of Bash scripts for deployment and
running. These scripts used password-less SSH to start everything on all the nodes in the cluster,
and were tailored only for the Linux environment. Running it on Windows, only used for testing purposes,
was a mostly manual affair.&lt;/p&gt;
&lt;p&gt;When I originally started planning to release Jumbo, back in 2013, I realized that maybe some
people would want to be able to try out Jumbo, if only in a one-node configuration, on Windows,
without having to manually launch multiple executables. I wrote some PowerShell scripts to facilitate
that, but of course, those were for Windows only, so now there were two sets of scripts for basically the
same thing.&lt;/p&gt;
&lt;p&gt;With the port to .Net Core, there was something else interesting I could use: &lt;a href="https://github.com/powershell/powershell"&gt;PowerShell Core&lt;/a&gt;!
Now, I could adapt the PowerShell scripts to work on both Windows and Linux, and get rid of
the old Bash scripts, rather than having to maintain two separate, functionally identical sets of
scripts. PowerShell Core even supported remote sessions over SSH now, so could be used to launch
Jumbo on multiple nodes in a Linux cluster.&lt;/p&gt;
&lt;p&gt;Come to think of it, there's really nothing to stop you from running a hybrid cluster with both
Windows and Linux nodes. I've never tried doing that. It could be interesting; it would probably
be somewhat difficult to get the configuration right, since e.g. the scripts assume Jumbo is in
the same location on every node, and the Windows paths need a drive letter. But it's probably
possible to hack something together.&lt;/p&gt;
&lt;p&gt;Anyway, that's the story of how Jumbo was brought into the modern age, and is no longer a pain
to run on either Windows or Linux. I eventually brought it forward to .Net 6 as well (which was very
straight-forward at that point), which is what's available now.&lt;/p&gt;
&lt;p&gt;Check out both &lt;a href="https://www.ookii.org/Link/JumboCore"&gt;Jumbo's .Net Core port&lt;/a&gt; and the &lt;a href="https://www.ookii.org/Link/Jumbo"&gt;original .Net/Mono version&lt;/a&gt;
on GitHub, and try to spot the differences!&lt;/p&gt;
</description>
      <pubDate>Mon, 17 Oct 2022 22:54:33 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/jumbo_and_netmono</guid>
      <link>https://www.ookii.org/Blog/jumbo_and_netmono</link>
      <title>Jumbo and .Net/Mono</title>
      <description>&lt;p&gt;This is the first in a series of articles about &lt;a href="https://www.ookii.org/Blog/Jumbo#additional-posts-about-jumbo"&gt;Jumbo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The recently released Jumbo comes in two versions: &lt;a href="https://www.ookii.org/Link/JumboCore"&gt;a modern version&lt;/a&gt;,
and &lt;a href="https://www.ookii.org/Link/Jumbo"&gt;the original&lt;/a&gt;. That original version was written in C#, primarily targeting
Mono, supporting the official Microsoft .Net Framework mainly for development and testing.&lt;/p&gt;
&lt;p&gt;Because it was dependent on rather old versions of Mono (I never tested it on newer versions),
and support for running on Windows was limited, it became harder to run it over time. Which is
partially why I eventually decided to port it to .Net Core.&lt;/p&gt;
&lt;p&gt;But, why is Jumbo written in .Net anyway? Hadoop is written in Java, so if I wanted to learn more
about Hadoop, wouldn't it make sense for me to use Java too? Perhaps, but I wasn't super
familiar with Java and its tooling, and I also felt it would steer me too much to be exactly like Hadoop, which I didn't
want either.&lt;/p&gt;
&lt;p&gt;So, I felt I had basically two choices given my skills at the time: C++ or .Net, both of which
I was very proficient in.&lt;/p&gt;
&lt;p&gt;C++ would've given me performance. But, it also would've increased the amount of work. I knew there were a bunch
of things I would need to solve that aren't straight-forward with C++: reading configuration, RPC across
a network, dynamically loading code to run jobs, text processing, robust networking, and more.
All of those would require me to either find libraries, or roll my own. Possible, but time consuming.&lt;/p&gt;
&lt;p&gt;Also keep in mind that this was 2008. C++11 was still called C++0x and not finished yet, with basically no compiler support. A lot
of the niceties of modern C++ weren't there yet. And the ecosystem for finding, building, and
using libraries in C++ wasn't exactly friendly either, especially if you wanted to work cross-platform.&lt;/p&gt;
&lt;p&gt;And I did want to be cross-platform: I had to run this thing on university-owned clusters, which
ran Linux, but I was doing my development on a Windows machine. And this was long before WSL made such a thing
easy to do; my best options at the time were VMs or Cygwin. The idea of using Visual Studio for
Linux development would've sounded ludicrous at the time, and VSCode wasn't even a flicker in
anyone's imagination yet.&lt;/p&gt;
&lt;p&gt;.Net would be slower, but, I reasoned, probably not any worse than Java. And running it on Linux
would be possible with &lt;a href="https://www.ookii.org/Link/Mono"&gt;Mono&lt;/a&gt;, I believed. It would give me most of what I needed for
free (like remoting and reflection), and I was kind of interested to see how some of .Net's features
over Java (such as real generics) would alter the design.&lt;/p&gt;
&lt;p&gt;My next choice would've been to use Rust, but unfortunately that hadn't been invented yet.&lt;/p&gt;
&lt;p&gt;Yeah, I guess part of the point of this post is that this whole endeavour would've been much easier
with modern tools. WSL, .Net Core, VSCode, maybe even Rust... it would've been interesting, to be sure.
I also used Subversion for source control; git was around, but not so prevalent yet. Only much later
did I move the repository over to git.&lt;/p&gt;
&lt;p&gt;Anyway, given my choices at the time, I picked .Net. I think it was a good choice, as it allowed me to develop something working reasonably
quickly. It also brought its challenges, in particular when dealing with Mono.&lt;/p&gt;
&lt;p&gt;At first, I just started in Visual Studio and tried to get some basic DFS parts running before
worrying too much about Linux. When I did try to run in on Linux with Mono, it worked surprisingly
well.&lt;/p&gt;
&lt;p&gt;Building it on Mono was another matter, however. I'm not 100% sure, but I seem to recall that
originally, the Mono C# compiler was lacking some features that prevented it. Even after it became
possible, I had to keep a separate build system; the Visual Studio (MSBuild) project files were used on
Windows, and for Mono I settled on &lt;a href="http://nant.sourceforge.net/"&gt;NAnt&lt;/a&gt;. I had to keep the two in
sync manually. Before that, I would just build on Windows, and run those binaries on Linux.&lt;/p&gt;
&lt;p&gt;Mono also limited me in what features I could use. .Net Framework 4.0 was in beta at the time,
but I couldn't adopt any fancy new features (like LINQ) until those were supported on Mono, which took a while.
Until then, I was basically stuck with mostly .Net Framework 2.0, as most of the 3.x features weren't
super relevant for what I was doing.&lt;/p&gt;
&lt;p&gt;One of the biggest hurdles with Mono turned out to be Garbage Collection, as Mono's GC at the time was much less advanced
than the .Net Framework (and presumably Java's) one. Mono's GC was non-generational, and used
a stop-the-world approach (all threads are paused during collection). It did eventually get a better
GC, called sgen, but it wasn't stable in time for me to benefit. Meanwhile, the old GC was often taking more than 10% of task
execution times, which necessitated the development of &lt;a href="https://github.com/SvenGroot/JumboCore/blob/main/doc/UserGuide/JetFeatures.md#record-reuse"&gt;record reuse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Another big challenge was some problems with .Net Remoting, which I'll cover separately in a future post.&lt;/p&gt;
&lt;p&gt;Still, Mono was what made this whole thing possible. Without it, I wouldn't have been able to use
.Net at all, and any other choice for me at the time would've made development more complicated,
slower, and probably less fun. Big thanks to Miguel de Icaza and others who contributed to Mono.&lt;/p&gt;
</description>
      <pubDate>Sat, 08 Oct 2022 20:35:16 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/jumbo</guid>
      <link>https://www.ookii.org/Blog/jumbo</link>
      <title>Jumbo</title>
      <description>&lt;p&gt;Today, I'm releasing something that I've wanted to release for a very long time. It's a project that I worked on
during my Ph.D., and while I don't think it'll be terribly useful to anyone, a lot of work went into it that
I want to preserve, even if just for myself.&lt;/p&gt;
&lt;p&gt;That project is &lt;em&gt;Jumbo&lt;/em&gt;, and it's now availabe on GitHub in two flavors: &lt;a href="https://www.ookii.org/Link/JumboCore"&gt;Jumbo for .Net 6+&lt;/a&gt;,
and &lt;a href="https://www.ookii.org/Link/Jumbo"&gt;the original for .Net Framework and Mono&lt;/a&gt;. If you want to play around with it or
learn more about it, you probably want the former.&lt;/p&gt;
&lt;p&gt;Jumbo is an experimental large-scale distributed data processing system, inspired by MapReduce and in
particular &lt;a href="https://hadoop.apache.org/"&gt;Hadoop 1.0&lt;/a&gt;. Jumbo was created as a way for me to learn about these systems, and should be
treated as such. It's &lt;em&gt;not&lt;/em&gt; production quality code, and you probably shouldn't entrust important
data to it.&lt;/p&gt;
&lt;p&gt;Basically, back when I was getting started with my Ph.D. in 2008, I found myself staring at the code of
Hadoop (which wasn't even at version 1.0 yet at the time), and finding I wasn't really getting a good
feel of how the whole thing fit together, and what really goes into designing a system like that.&lt;/p&gt;
&lt;p&gt;So, some people at my lab suggested I should try building something for myself, which I did. I built,
from the ground up, a distributed file system and data processing system, which is Jumbo. It was
heavily inspired by Hadoop, and definitely borrows from its design (although no actual code was
borrowed). In some aspects, I deviate from Hadoop quite a lot (especially since Jumbo isn't
constrained to only using MapReduce).&lt;/p&gt;
&lt;p&gt;Building Jumbo taught me a lot: about software design, about distributed processing, about decisions
that affect scalability, and more. It's my hope that maybe, someone else interested in these topics might
want to look at it and find what I did interesting. If nothing else, I just want to preserve this
massive project that I did (still the biggest project I've done where I'm the sole contributor), and
have its history available.&lt;/p&gt;
&lt;p&gt;I did end up using Jumbo for some research efforts, which you can read about in a few papers as well
as my dissertation under the &lt;a href="https://www.ookii.org/University"&gt;University&lt;/a&gt; section of my site.&lt;/p&gt;
&lt;p&gt;Jumbo is also the origin of one of my most widely used libraries, &lt;a href="https://www.ookii.org/Software/CommandLineParser"&gt;Ookii.CommandLine&lt;/a&gt;,
so it's significant in that respect as well.&lt;/p&gt;
&lt;p&gt;Like I said, I've wanted to release Jumbo for a long time. If you look through the &lt;a href="https://www.ookii.org/Link/Jumbo"&gt;original project's&lt;/a&gt;
commit history you can see a bunch of work done in early 2013 (as I was nearing the end of my Ph.D.)
like cleaning stuff up and adding documentation, but I never quite reached a level where I was
comfortable doing so. The project, which primarily targeted Mono to run on Linux, wasn't that easy to set up
and run.&lt;/p&gt;
&lt;p&gt;In 2019, I ported the project to .Net Core, just to see if I could. That version was easier to play
around with, and I wanted to release it then too, but I never quite got around to finishing it, until
now.&lt;/p&gt;
&lt;p&gt;So now, you can look at Jumbo and play around with it on .Net 6+, thanks to this &lt;a href="https://www.ookii.org/Link/JumboCore"&gt;new version&lt;/a&gt;.
I've also expanded the documentation significantly, so it should be easy to get started and to learn
more about how it works. The original Jumbo project for Mono and .Net Framework is only provided to
preserve the original history of the project (the new repository only contains the history of the port).
You probably shouldn't try and run it (though I obviously can't stop you).&lt;/p&gt;
&lt;p&gt;If you want to comment on Jumbo or ask any questions, please use the discussions page on GitHub.&lt;/p&gt;
&lt;h3 id="additional-posts-about-jumbo"&gt;Additional posts about Jumbo&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ookii.org/Blog/jumbo_and_netmono"&gt;Jumbo and .Net/Mono&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ookii.org/Blog/a_tale_of_two_jumbos"&gt;A tale of two Jumbos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ookii.org/Blog/jumbo_and_net_remoting"&gt;Jumbo and .Net Remoting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ookii.org/Blog/jumbos_jobbuilder_part_1"&gt;Jumbo's JobBuilder: part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ookii.org/Blog/jumbos_jobbuilder_part_2"&gt;Jumbo's JobBuilder: part 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Tue, 20 Sep 2022 23:54:43 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/ookiiformatc_23</guid>
      <link>https://www.ookii.org/Blog/ookiiformatc_23</link>
      <title>Ookii.FormatC 2.3</title>
      <description>&lt;p&gt;After I recently released an updated version of &lt;a href="https://www.ookii.org/Blog/ookiicommandline_24"&gt;Ookii.CommandLine&lt;/a&gt;, I figured &lt;a href="https://www.ookii.org/Software/FormatC"&gt;Ookii.FormatC&lt;/a&gt; could also use some love.&lt;/p&gt;
&lt;p&gt;This version comes with an optional new dark mode stylesheet, nullable reference types enabled for the library, the ability to write directly to a &lt;code&gt;TextWriter&lt;/code&gt;, C# 10.0 keyword support, and a few minor other features and fixes.&lt;/p&gt;
&lt;p&gt;Thanks to the ability to write to a &lt;code&gt;TextWriter&lt;/code&gt;, you can now do stuff like this:&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span class="keyword"&gt;var&lt;/span&gt; formatter = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;CodeFormatter&lt;/span&gt;()
{
    FormattingInfo = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;CSharpFormattingInfo&lt;/span&gt;()
};

formatter.FormatCode(SampleCode, &lt;span class="type"&gt;Console&lt;/span&gt;.Out);&lt;/pre&gt;
&lt;p&gt;Okay, writing HTML to the console is maybe not the most useful example, but you get the idea.&lt;/p&gt;
&lt;p&gt;You can try it out with &lt;a href="https://dotnetfiddle.net/rO80Or"&gt;.Net Fiddle&lt;/a&gt;, or look at a &lt;a href="https://www.ookii.org/Link/FormatCSample"&gt;sample&lt;/a&gt; that also shows the new dark mode in action. The &lt;a href="https://www.ookii.org/Software/FormatC/Highlight"&gt;online syntax highlighter&lt;/a&gt; has also been updated, and now supports &lt;code&gt;PSParser&lt;/code&gt; based PowerShell formatting again.&lt;/p&gt;
&lt;p&gt;And yeah, the &lt;a href="https://www.ookii.org/Link/FormatCNuGet"&gt;NuGet package&lt;/a&gt; is version 2.3.1, rather than 2.3.0. That's because somehow the package for 2.3.0 ended up with an outdated binary in it. Not sure how that's possible, but it happened.&lt;/p&gt;
</description>
      <pubDate>Wed, 14 Sep 2022 05:40:49 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/ookiicommandline_24</guid>
      <link>https://www.ookii.org/Blog/ookiicommandline_24</link>
      <title>Ookii.CommandLine 2.4</title>
      <description>&lt;p&gt;I've released an update to Ookii.CommandLine, my library for parsing command line arguments for .Net.&lt;/p&gt;
&lt;p&gt;This new version comes with nullable reference type support (for .Net 6+), a new helper to make parsing easier, more customizability, an easier way to make &lt;code&gt;-Help&lt;/code&gt; style arguments, and some bug fixes.&lt;/p&gt;
&lt;p&gt;See the full list of changes &lt;a href="https://www.ookii.org/Link/CommandLineVersionHistory"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With the new helper method, you can now just do the following to parse the arguments and write errors and usage to the console if parsing failed:&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span class="keyword"&gt;var&lt;/span&gt; parsed = &lt;span class="type"&gt;CommandLineParser&lt;/span&gt;.Parse&amp;lt;&lt;span class="type"&gt;MyArguments&lt;/span&gt;&amp;gt;(args);&lt;/pre&gt;
&lt;p&gt;And if you want to customize parsing behavior, you can still do so with this method:&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span class="keyword"&gt;var&lt;/span&gt; options = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;ParseOptions&lt;/span&gt;()
{
    NameValueSeparator = &lt;span class="string"&gt;'='&lt;/span&gt;
};

&lt;span class="keyword"&gt;var&lt;/span&gt; parsed = &lt;span class="type"&gt;CommandLineParser&lt;/span&gt;.Parse&amp;lt;&lt;span class="type"&gt;MyArguments&lt;/span&gt;&amp;gt;(args, options);&lt;/pre&gt;
&lt;p&gt;Of course, existing code to parse arguments that manually creates an instance of &lt;code&gt;CommandLineParser&lt;/code&gt; will continue to work.&lt;/p&gt;
&lt;p&gt;Check it out on &lt;a href="https://www.ookii.org/Link/NugetCommandLine"&gt;NuGet&lt;/a&gt; or &lt;a href="https://www.ookii.org/Link/CommandLineGitHub"&gt;GitHub&lt;/a&gt;, or &lt;a href="https://dotnetfiddle.net/fgLvSl"&gt;try it out online!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Also, the Visual Studio code snippets (which previously required manual installation) are now available on the &lt;a href="https://www.ookii.org/Link/CommandLineSnippets"&gt;Visual Studio marketplace&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Tue, 06 Sep 2022 03:05:10 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/some_small_site_updates</guid>
      <link>https://www.ookii.org/Blog/some_small_site_updates</link>
      <title>Some small site updates</title>
      <description>&lt;p&gt;I know I don't exactly do much with this site anymore, but I did recently make some small changes.&lt;/p&gt;
&lt;p&gt;Primarily, I've reorganized some of the outdated content (like the stuff related to Channel 9) so it's less prominent. I've also updated the information for &lt;a href="https://www.ookii.org/Software/Dialogs"&gt;Ookii.Dialogs&lt;/a&gt; to link to the &lt;a href="https://www.ookii.org/Link/DialogsGitHub"&gt;forked project on GitHub&lt;/a&gt;. My own version isn't kept up to date, so most people looking for Ookii.Dialogs should probably be using that version instead (the people who are doing this are &lt;em&gt;awesome&lt;/em&gt;, by the way, for keeping this project alive).&lt;/p&gt;
&lt;p&gt;Oh, and the site now supports https, thanks to Azure's free SSL/TLS certificates.&lt;/p&gt;
</description>
      <pubDate>Thu, 25 Aug 2022 19:56:36 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/a_new_home_for_ookiiorg</guid>
      <link>https://www.ookii.org/Blog/a_new_home_for_ookiiorg</link>
      <title>A new home for ookii.org</title>
      <description>&lt;p&gt;Ookii.org now has a new home! It&amp;#39;s the same site as always, except now it&amp;#39;s hosted on &lt;a href="https://azure.microsoft.com"&gt;Microsoft Azure&lt;/a&gt;. This shouldn&amp;#39;t make any difference for you, and it probably won&amp;#39;t mean this blog will get any more active, but all the existing content is still there.&lt;br /&gt;&lt;/p&gt;</description>
      <pubDate>Fri, 30 Jun 2017 00:17:59 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/comments_are_now_closed</guid>
      <link>https://www.ookii.org/Blog/comments_are_now_closed</link>
      <title>Comments are now closed</title>
      <description>&lt;p&gt;Due to the very large volume of spam comments this site receives despite efforts on my part to block them (recaptcha is apparently completely useless), I&amp;#39;m unfortunately forced to disable comments on all posts on this site.&lt;/p&gt;&lt;p&gt;If you wish to contact me about anything pertaining to this site, please &lt;a href="https://twitter.com/dilandau3000"&gt;use Twitter&lt;/a&gt; instead.&lt;/p&gt;&lt;p&gt;I apologize for the inconvenience.&lt;/p&gt;</description>
      <pubDate>Sat, 27 Sep 2014 20:35:00 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/net_configuration_section_documentation_generator</guid>
      <link>https://www.ookii.org/Blog/net_configuration_section_documentation_generator</link>
      <title>.Net Configuration Section Documentation Generator</title>
      <description>&lt;p&gt;Today I&amp;#39;m releasing another small tool that I created for personal use that I believe others might find of use. The tool is the .Net Configuration Section Documentation Generator (more proof that I should not be put in charge of naming things), which does what it says on the tin: it generates documentation for configuration sections defined using .Net&amp;#39;s &lt;code&gt;ConfigurationSection&lt;/code&gt; class.&lt;/p&gt;&lt;p&gt;Basically, it can generate an XSD schema from a &lt;code&gt;ConfigurationSection&lt;/code&gt;, to which you can then add annotations to document the elements and attributes of the section, and then generate an HTML documentation file from that schema. The tool can be used with Microsoft .Net and Mono.&lt;/p&gt;&lt;p&gt;As an example, check out the &lt;a href="http://www.ookii.org/Content/software/ConfigurationDocumentationGeneratorConfig.xsd"&gt;schema for the documentation generator&amp;#39;s own configuration section&lt;/a&gt; as generated by the documentation generator, and with &lt;a href="http://www.ookii.org/Content/software/ConfigurationDocumentationGeneratorConfigAnnotated.xsd"&gt;added annotations&lt;/a&gt;. And finally, the &lt;a href="http://www.ookii.org/Content/software/ConfigurationDocumentationGeneratorConfig.html"&gt;documentation file&lt;/a&gt; generated from the annotated schema. This is only a very trivial configuration section, but it gives you an idea of what the output of the documentation generator looks like.&lt;/p&gt;&lt;p&gt;More information and downloads &lt;a href="http://www.ookii.org/Software/ConfigurationDocumentationGenerator"&gt;here&lt;/a&gt;.&lt;/p&gt;</description>
      <pubDate>Sat, 08 Jun 2013 10:42:49 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/creating_a_route_for_a_asmx_web_service_with_aspnet_routing</guid>
      <link>https://www.ookii.org/Blog/creating_a_route_for_a_asmx_web_service_with_aspnet_routing</link>
      <title>Creating a route for a .asmx Web Service with ASP.NET routing</title>
      <description>&lt;p&gt;While working on the &lt;a href="http://www.ookii.org/Blog/welcome_to_the_new_ookiiorg"&gt;update to my site&lt;/a&gt; (which is now completely finished), I came across a minor problem: I had an old ASP.NET Web Service (you know, the .asmx type, predating even WCF) that I needed to preserve at the same URL for compatibility purposes.&lt;/p&gt;&lt;p&gt;However, that URL was inside a folder on the old site. And in the new ASP.NET MVC site, that folder didn&amp;#39;t exist anymore; instead, requests for that path were handled by an MVC Controller. This meant that it was not possible to leave the .asmx file at its original location, because an existing physical directory pre-empts the MVC route for the controller, so it would render all the other pages with the same base URL unviewable.&lt;/p&gt;&lt;p&gt;I figured I&amp;#39;d be able to use &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.routing.routecollection.mappageroute.aspx"&gt;&lt;code&gt;RouteCollection.MapPageRoute&lt;/code&gt;&lt;/a&gt; to redirect the old URL to the .asmx file&amp;#39;s new location, but it turned out that this function only works for pages (.aspx files), as it requires that the HTTP handler for the route&amp;#39;s target derives from &lt;code&gt;System.Web.Page&lt;/code&gt;. Searching the web wasn&amp;#39;t able to find a solution for this, but some investigation into the inner workings of ASP.NET routing let me devise my own solution, which I thought I&amp;#39;d share here.&lt;/p&gt;&lt;p&gt;The trick, it turns out, is to create a custom &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.routing.iroutehandler.aspx"&gt;&lt;code&gt;IRouteHandler&lt;/code&gt;&lt;/a&gt;, one that is able to return the correct &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.ihttphandler.aspx"&gt;&lt;code&gt;IHttpHandler&lt;/code&gt;&lt;/a&gt; for web services. This turned out to be fairly simple:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="keyword"&gt;using&lt;/span&gt; System;
&lt;span class="keyword"&gt;using&lt;/span&gt; System.Web;
&lt;span class="keyword"&gt;using&lt;/span&gt; System.Web.Routing;
&lt;span class="keyword"&gt;using&lt;/span&gt; System.Web.Services.Protocols;

&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="type"&gt;ServiceRouteHandler&lt;/span&gt; : &lt;span class="type"&gt;IRouteHandler&lt;/span&gt;
{
    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;readonly&lt;/span&gt; &lt;span class="keyword"&gt;string&lt;/span&gt; _virtualPath;
    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;readonly&lt;/span&gt; &lt;span class="type"&gt;WebServiceHandlerFactory&lt;/span&gt; _handlerFactory = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;WebServiceHandlerFactory&lt;/span&gt;();

    &lt;span class="keyword"&gt;public&lt;/span&gt; ServiceRouteHandler(&lt;span class="keyword"&gt;string&lt;/span&gt; virtualPath)
    {
        &lt;span class="keyword"&gt;if&lt;/span&gt;( virtualPath == &lt;span class="keyword"&gt;null&lt;/span&gt; )
            &lt;span class="keyword"&gt;throw&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;ArgumentNullException&lt;/span&gt;(&lt;span class="string"&gt;"virtualPath"&lt;/span&gt;);
        &lt;span class="keyword"&gt;if&lt;/span&gt;( !virtualPath.StartsWith(&lt;span class="string"&gt;"~/"&lt;/span&gt;) )
            &lt;span class="keyword"&gt;throw&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;ArgumentException&lt;/span&gt;(&lt;span class="string"&gt;"Virtual path must start with ~/"&lt;/span&gt;, &lt;span class="string"&gt;"virtualPath"&lt;/span&gt;);
        _virtualPath = virtualPath;
    }

    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="type"&gt;IHttpHandler&lt;/span&gt; GetHttpHandler(&lt;span class="type"&gt;RequestContext&lt;/span&gt; requestContext)
    {
        &lt;span class="comment"&gt;// Note: can't pass requestContext.HttpContext as the first parameter because that's&lt;/span&gt;
        &lt;span class="comment"&gt;// type HttpContextBase, while GetHandler wants HttpContext.&lt;/span&gt;
        &lt;span class="keyword"&gt;return&lt;/span&gt; _handlerFactory.GetHandler(&lt;span class="type"&gt;HttpContext&lt;/span&gt;.Current, requestContext.HttpContext.Request.HttpMethod, _virtualPath, requestContext.HttpContext.Server.MapPath(_virtualPath));
    }
}&lt;/pre&gt;&lt;p&gt;This route handler can then be used to add a route for a .asmx web service:&lt;/p&gt;&lt;pre class="code"&gt;routes.Add(&lt;span class="string"&gt;"RouteName"&lt;/span&gt;, &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;Route&lt;/span&gt;(&lt;span class="string"&gt;"path/to/your/service"&lt;/span&gt;, &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;RouteValueDictionary&lt;/span&gt;() { { &lt;span class="string"&gt;"controller"&lt;/span&gt;, &lt;span class="keyword"&gt;null&lt;/span&gt; }, { &lt;span class="string"&gt;"action"&lt;/span&gt;, &lt;span class="keyword"&gt;null&lt;/span&gt; } }, &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;ServiceRouteHandler&lt;/span&gt;(&lt;span class="string"&gt;"~/actualservice.asmx"&lt;/span&gt;)));&lt;/pre&gt;&lt;p&gt;One important thing to note is the &lt;code&gt;RouteValueDictionary&lt;/code&gt; values I&amp;#39;m passing to the route. If you don&amp;#39;t add values for &amp;quot;controller&amp;quot; and &amp;quot;action&amp;quot; to the dictionary, the &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.mvc.html.linkextensions.actionlink.aspx"&gt;Html.ActionLink&lt;/a&gt; helper method will match this route for any controller or action, leading to completely incorrect URLs if this is the first matching route. Since you probably want to have this route before the MVC default route (which was the case for me), that would be a problem. Setting the values in the route value dictionary resolves this issue.&lt;/p&gt;&lt;p&gt;Of course, you can create your own extension method to make this easier:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="type"&gt;Route&lt;/span&gt; MapServiceRoute(&lt;span class="keyword"&gt;this&lt;/span&gt; &lt;span class="type"&gt;RouteCollection&lt;/span&gt; routes, &lt;span class="keyword"&gt;string&lt;/span&gt; routeName, &lt;span class="keyword"&gt;string&lt;/span&gt; url, &lt;span class="keyword"&gt;string&lt;/span&gt; virtualPath)
{
    &lt;span class="keyword"&gt;if&lt;/span&gt;( routes == &lt;span class="keyword"&gt;null&lt;/span&gt; )
        &lt;span class="keyword"&gt;throw&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;ArgumentNullException&lt;/span&gt;(&lt;span class="string"&gt;"routes"&lt;/span&gt;);
    &lt;span class="type"&gt;Route&lt;/span&gt; route = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;Route&lt;/span&gt;(url, &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;RouteValueDictionary&lt;/span&gt;() { { &lt;span class="string"&gt;"controller"&lt;/span&gt;, &lt;span class="keyword"&gt;null&lt;/span&gt; }, { &lt;span class="string"&gt;"action"&lt;/span&gt;, &lt;span class="keyword"&gt;null&lt;/span&gt; } }, &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;ServiceRouteHandler&lt;/span&gt;(virtualPath));
    routes.Add(routeName, route);
    &lt;span class="keyword"&gt;return&lt;/span&gt; route;
}&lt;/pre&gt;&lt;p&gt;Which means you can now simply use this:&lt;/p&gt;&lt;pre class="code"&gt;routes.MapServiceRoute(&lt;span class="string"&gt;"RouteName"&lt;/span&gt;, &lt;span class="string"&gt;"path/to/your/service"&lt;/span&gt;, &lt;span class="string"&gt;"~/actualservice.asmx"&lt;/span&gt;);&lt;/pre&gt;&lt;p&gt;And voil&amp;#224;, I was now able to preserve the URL to my legacy web service without needing to have a physical file matching that URL. If you&amp;#39;re faced with a similar situation, I hope this helps you.&lt;/p&gt;</description>
      <pubDate>Thu, 30 May 2013 15:56:44 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/welcome_to_the_new_ookiiorg</guid>
      <link>https://www.ookii.org/Blog/welcome_to_the_new_ookiiorg</link>
      <title>Welcome to the new Ookii.org</title>
      <description>&lt;p&gt;As you can see, Ookii.org has gotten a revamp. Now you may wonder, why make such a radical update to a site when the content doesn&amp;#39;t change that often? The main reason is that I wanted to get my hands dirty with some modern web technology.&lt;/p&gt;&lt;p&gt;While web development has never been particularly important to me, it was something I knew how to do and I kept somewhat up-to-date with. This dates back to making the site for my high school (at the time in Frontpage). However, as I&amp;#39;d been busy with my Ph.D. in Japan, I had very little time to keep up to date with stuff I didn&amp;#39;t have any need to use in my research. Now that I&amp;#39;ve finished and haven&amp;#39;t started my new job yet, it was the ideal time to brush up a little.&lt;/p&gt;&lt;p&gt;Back when I started this site, I wrote it in ASP.NET 2.0. I hand-rolled an AJAX library, because jQuery was years from being invented and actually AJAX support in websites wasn&amp;#39;t all that common yet. The AJAX parts of the site used web services that returned data in XML, because no one had thought to start using JSON yet either. &amp;quot;Mobile browsers&amp;quot; at the time meant IE for Windows Mobile, which didn&amp;#39;t support script at all and was barely on par with IE6 on the desktop otherwise. And speaking of IE6, supporting that was still very much a priority at the time.&lt;/p&gt;&lt;p&gt;But things have changed, and the goal was to re-familiarize myself with the state of the art. So this site is written in &lt;a href="http://www.asp.net/mvc"&gt;ASP.NET MVC 4&lt;/a&gt;, using the &lt;a href="http://msdn.microsoft.com/en-us/data/ef.aspx"&gt;Entity Framework&lt;/a&gt; for data access. On the client, it uses HTML5 and CSS3, with scripts utilizing &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; and a few related libraries. The site now has a responsive layout thanks to &lt;a href="http://foundation.zurb.com/"&gt;Zurb Foundation&lt;/a&gt; (try it: resize your browser window and watch the layout change), which has the side-effect of making it much more friendly for mobile devices. I&amp;#39;ve also used &lt;a href="http://knockoutjs.com/"&gt;Knockout&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;I have to say I was surprised at how much web programming has changed. ASP.NET MVC is light-years beyond the old web forms. Not only are the frameworks much better and the HTML/CSS much cleaner (at least, as long as you only target modern browsers), the tooling (in the form of Visual Studio 2012 in my case) also has improved tremendously.&lt;/p&gt;&lt;p&gt;Besides just redesigning the layout, I&amp;#39;ve also made a new front page that de-emphasizes the blog a little (since I don&amp;#39;t update it that often), and instead provides a few more useful links as well as news about my &lt;a href="http://www.youtube.com/user/Dilandau3000"&gt;Let&amp;#39;s Plays&lt;/a&gt; from the associated Twitter account.&lt;/p&gt;&lt;p&gt;Note that not everything uses the new layout. Currently, only the blog has been updated. I intend to move other sections of the site to the new layout over time.&lt;/p&gt;&lt;p&gt;Let me know what you think, or if you find something that&amp;#39;s broken.&lt;/p&gt;</description>
      <pubDate>Tue, 07 May 2013 19:50:44 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/ookiicommandline_22</guid>
      <link>https://www.ookii.org/Blog/ookiicommandline_22</link>
      <title>Ookii.CommandLine 2.2</title>
      <description>&lt;p&gt;Yes, I realise this blog is all but dead. However, I will soon finally complete my Ph.D. at the University of Tokyo (so, yay me!), and as a result I can now finally release a few things that had been put on hold while I was writing my Ph.D. thesis. In addition, I will probably release a few projects related to my research that may hopefully be of interest to some people.&lt;/p&gt;&lt;p&gt;First up is a small update to Ookii.CommandLine. This update adds command line argument aliases, some updates for usage help generation, and code snippets to help with creating argument classes.&lt;/p&gt;&lt;p&gt;In addition, I used NuGet to create a symbols package and published it on SymbolSource, which you should apparently be able to use as a symbol server in Visual Studio without manually downloading the symbols.&lt;/p&gt;&lt;p&gt;Get the release from &lt;a href="http://www.ookii.org/link.ashx?id=CodePlexCommandLine"&gt;CodePlex&lt;/a&gt; or using &lt;a href="http://www.ookii.org/link.ashx?id=NuGetCommandLine"&gt;NuGet&lt;/a&gt;.&lt;/p&gt;</description>
      <pubDate>Wed, 06 Feb 2013 12:11:56 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
    <item>
      <guid isPermaLink="true">https://www.ookii.org/Blog/floppy_support_in_windows_virtual_pc_7</guid>
      <link>https://www.ookii.org/Blog/floppy_support_in_windows_virtual_pc_7</link>
      <title>Floppy support in Windows Virtual PC 7</title>
      <description>&lt;p&gt;&lt;a href="http://www.microsoft.com/windows/virtual-pc/"&gt;Windows Virtual PC 7&lt;/a&gt; is the successor to Virtual PC 2007, and the only version of Virtual PC that can run on Windows 7. It&amp;#39;s freely available to anyone, and if you have Windows 7 Professional or Ultimate, you can also download Windows XP Mode, a virtual machine of Windows XP that allows you to run applications that may not otherwise work on Windows 7.&lt;/p&gt;&lt;p&gt;It seems however that Microsoft has removed the UI for using floppy disks with Windows Virtual PC 7. It was there in Virtual PC 2007, and now it&amp;#39;s not. However, here&amp;#39;s the thing: they &lt;em&gt;only&lt;/em&gt; removed the UI, not the functionality itself.&lt;/p&gt;&lt;p&gt;The functionality for using floppy disks is still available in Windows Virtual PC 7, but it&amp;#39;s only accessible via the COM object, which is not very convenient. Fortunately, Windows 7 also includes PowerShell, a very powerful shell scripting environment, that allows us to make things a bit easier.&lt;/p&gt;&lt;p&gt;Below, I present three PowerShell scripts: one to create virtual floppy disk images, one to attach floppy disks to a VM, and one to release them. The latter two can be used with both virtual floppy disk images, or a real floppy drive on the host computer.&lt;/p&gt;&lt;p&gt;You can &lt;a href="http://www.ookii.org/download.ashx?id=VirtualFloppyScripts"&gt;download all three scripts here&lt;/a&gt;. Or you can just copy/paste them into a file, whichever you prefer. Note that you&amp;#39;ll need to &lt;a href="http://technet.microsoft.com/en-us/library/dd347649.aspx"&gt;configure PowerShell to allow unsigned scripts to run&lt;/a&gt;, or sign the scripts.&lt;/p&gt;&lt;p&gt;Anyway, here are the scripts:&lt;/p&gt;&lt;p&gt;Create-VirtualFloppy.ps1:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="psKeyword"&gt;param&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;
    &lt;span class="psOperator"&gt;[&lt;/span&gt;&lt;span class="psAttribute"&gt;parameter&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psMember"&gt;Position&lt;/span&gt;&lt;span class="psOperator"&gt;=&lt;/span&gt;&lt;span class="psNumber"&gt;0&lt;/span&gt;&lt;span class="psOperator"&gt;,&lt;/span&gt; &lt;span class="psMember"&gt;Mandatory&lt;/span&gt;&lt;span class="psOperator"&gt;=&lt;/span&gt;&lt;span class="psVariable"&gt;$true&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;]&lt;/span&gt;&lt;span class="psType"&gt;[string]&lt;/span&gt;&lt;span class="psVariable"&gt;$Path&lt;/span&gt;
&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;

&lt;span class="psVariable"&gt;$vpc&lt;/span&gt; &lt;span class="psOperator"&gt;=&lt;/span&gt; &lt;span class="psCommand"&gt;New-Object&lt;/span&gt; &lt;span class="psCommandParameter"&gt;-ComObject&lt;/span&gt; &lt;span class="psString"&gt;"VirtualPC.Application"&lt;/span&gt;
&lt;span class="psKeyword"&gt;if&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt; &lt;span class="psOperator"&gt;-not&lt;/span&gt; &lt;span class="psType"&gt;[System.IO.Path]&lt;/span&gt;&lt;span class="psOperator"&gt;::&lt;/span&gt;&lt;span class="psMember"&gt;IsPathRooted&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psVariable"&gt;$Path&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt; &lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
&lt;span class="psGroupStart"&gt;{&lt;/span&gt;
    &lt;span class="psComment"&gt;# CreateFloppyDiskImage requires an absolute path&lt;/span&gt;
    &lt;span class="psType"&gt;[Environment]&lt;/span&gt;&lt;span class="psOperator"&gt;::&lt;/span&gt;&lt;span class="psMember"&gt;CurrentDirectory&lt;/span&gt; &lt;span class="psOperator"&gt;=&lt;/span&gt; &lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psCommand"&gt;Get-Location&lt;/span&gt; &lt;span class="psCommandParameter"&gt;-PSProvider&lt;/span&gt; &lt;span class="psCommandArgument"&gt;FileSystem&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;ProviderPath&lt;/span&gt;
    &lt;span class="psVariable"&gt;$Path&lt;/span&gt; &lt;span class="psOperator"&gt;=&lt;/span&gt; &lt;span class="psType"&gt;[System.IO.Path]&lt;/span&gt;&lt;span class="psOperator"&gt;::&lt;/span&gt;&lt;span class="psMember"&gt;GetFullPath&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psVariable"&gt;$Path&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
&lt;span class="psGroupEnd"&gt;}&lt;/span&gt;
&lt;span class="psVariable"&gt;$vpc&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;CreateFloppyDiskImage&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psVariable"&gt;$Path&lt;/span&gt;&lt;span class="psOperator"&gt;,&lt;/span&gt; &lt;span class="psNumber"&gt;2&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Attach-VirtualFloppy.ps1:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="psKeyword"&gt;param&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;
  &lt;span class="psOperator"&gt;[&lt;/span&gt;&lt;span class="psAttribute"&gt;parameter&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psMember"&gt;Position&lt;/span&gt;&lt;span class="psOperator"&gt;=&lt;/span&gt;&lt;span class="psNumber"&gt;0&lt;/span&gt;&lt;span class="psOperator"&gt;,&lt;/span&gt; &lt;span class="psMember"&gt;Mandatory&lt;/span&gt;&lt;span class="psOperator"&gt;=&lt;/span&gt;&lt;span class="psVariable"&gt;$true&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;]&lt;/span&gt;&lt;span class="psType"&gt;[string]&lt;/span&gt;&lt;span class="psVariable"&gt;$VirtualMachineName&lt;/span&gt;&lt;span class="psOperator"&gt;,&lt;/span&gt;
  &lt;span class="psOperator"&gt;[&lt;/span&gt;&lt;span class="psAttribute"&gt;parameter&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psMember"&gt;Position&lt;/span&gt;&lt;span class="psOperator"&gt;=&lt;/span&gt;&lt;span class="psNumber"&gt;1&lt;/span&gt;&lt;span class="psOperator"&gt;,&lt;/span&gt; &lt;span class="psMember"&gt;Mandatory&lt;/span&gt;&lt;span class="psOperator"&gt;=&lt;/span&gt;&lt;span class="psVariable"&gt;$true&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;]&lt;/span&gt;&lt;span class="psType"&gt;[string]&lt;/span&gt;&lt;span class="psVariable"&gt;$FloppyPath&lt;/span&gt;&lt;span class="psOperator"&gt;,&lt;/span&gt;
  &lt;span class="psOperator"&gt;[&lt;/span&gt;&lt;span class="psAttribute"&gt;parameter&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psMember"&gt;Mandatory&lt;/span&gt;&lt;span class="psOperator"&gt;=&lt;/span&gt;&lt;span class="psVariable"&gt;$false&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;]&lt;/span&gt;&lt;span class="psType"&gt;[Switch]&lt;/span&gt;&lt;span class="psVariable"&gt;$HostDrive&lt;/span&gt;
&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;

&lt;span class="psVariable"&gt;$vpc&lt;/span&gt; &lt;span class="psOperator"&gt;=&lt;/span&gt; &lt;span class="psCommand"&gt;New-Object&lt;/span&gt; &lt;span class="psCommandParameter"&gt;-ComObject&lt;/span&gt; &lt;span class="psString"&gt;"VirtualPC.Application"&lt;/span&gt;
&lt;span class="psVariable"&gt;$vm&lt;/span&gt; &lt;span class="psOperator"&gt;=&lt;/span&gt; &lt;span class="psVariable"&gt;$vpc&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;FindVirtualMachine&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psVariable"&gt;$VirtualMachineName&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;

&lt;span class="psKeyword"&gt;if&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt; &lt;span class="psVariable"&gt;$vm&lt;/span&gt; &lt;span class="psOperator"&gt;-eq&lt;/span&gt; &lt;span class="psVariable"&gt;$null&lt;/span&gt; &lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
&lt;span class="psGroupStart"&gt;{&lt;/span&gt;
    &lt;span class="psCommand"&gt;Write-Error&lt;/span&gt; &lt;span class="psString"&gt;"Virtual machine $vm not found."&lt;/span&gt;
&lt;span class="psGroupEnd"&gt;}&lt;/span&gt;
&lt;span class="psKeyword"&gt;else&lt;/span&gt;
&lt;span class="psGroupStart"&gt;{&lt;/span&gt;
    &lt;span class="psKeyword"&gt;if&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt; &lt;span class="psVariable"&gt;$HostDrive&lt;/span&gt; &lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
    &lt;span class="psGroupStart"&gt;{&lt;/span&gt;
        &lt;span class="psVariable"&gt;$vm&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;FloppyDrives&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;Item&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psNumber"&gt;1&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;AttachHostDrive&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psVariable"&gt;$FloppyPath&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
    &lt;span class="psGroupEnd"&gt;}&lt;/span&gt;
    &lt;span class="psKeyword"&gt;else&lt;/span&gt;
    &lt;span class="psGroupStart"&gt;{&lt;/span&gt;
        &lt;span class="psKeyword"&gt;if&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt; &lt;span class="psOperator"&gt;-not&lt;/span&gt; &lt;span class="psType"&gt;[System.IO.Path]&lt;/span&gt;&lt;span class="psOperator"&gt;::&lt;/span&gt;&lt;span class="psMember"&gt;IsPathRooted&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psVariable"&gt;$FloppyPath&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt; &lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
        &lt;span class="psGroupStart"&gt;{&lt;/span&gt;
            &lt;span class="psComment"&gt;# AttachImage requires an absolute path&lt;/span&gt;
            &lt;span class="psType"&gt;[Environment]&lt;/span&gt;&lt;span class="psOperator"&gt;::&lt;/span&gt;&lt;span class="psMember"&gt;CurrentDirectory&lt;/span&gt; &lt;span class="psOperator"&gt;=&lt;/span&gt; &lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psCommand"&gt;Get-Location&lt;/span&gt; &lt;span class="psCommandParameter"&gt;-PSProvider&lt;/span&gt; &lt;span class="psCommandArgument"&gt;FileSystem&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;ProviderPath&lt;/span&gt;
            &lt;span class="psVariable"&gt;$FloppyPath&lt;/span&gt; &lt;span class="psOperator"&gt;=&lt;/span&gt; &lt;span class="psType"&gt;[System.IO.Path]&lt;/span&gt;&lt;span class="psOperator"&gt;::&lt;/span&gt;&lt;span class="psMember"&gt;GetFullPath&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psVariable"&gt;$FloppyPath&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
        &lt;span class="psGroupEnd"&gt;}&lt;/span&gt;
        &lt;span class="psVariable"&gt;$vm&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;FloppyDrives&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;Item&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psNumber"&gt;1&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;AttachImage&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psVariable"&gt;$FloppyPath&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
    &lt;span class="psGroupEnd"&gt;}&lt;/span&gt;
&lt;span class="psGroupEnd"&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Release-VirtualFloppy.ps1:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="psKeyword"&gt;param&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;
  &lt;span class="psOperator"&gt;[&lt;/span&gt;&lt;span class="psAttribute"&gt;parameter&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psMember"&gt;Position&lt;/span&gt;&lt;span class="psOperator"&gt;=&lt;/span&gt;&lt;span class="psNumber"&gt;0&lt;/span&gt;&lt;span class="psOperator"&gt;,&lt;/span&gt; &lt;span class="psMember"&gt;Mandatory&lt;/span&gt;&lt;span class="psOperator"&gt;=&lt;/span&gt;&lt;span class="psVariable"&gt;$true&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;]&lt;/span&gt;&lt;span class="psType"&gt;[string]&lt;/span&gt;&lt;span class="psVariable"&gt;$VirtualMachineName&lt;/span&gt;
&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;

&lt;span class="psVariable"&gt;$vpc&lt;/span&gt; &lt;span class="psOperator"&gt;=&lt;/span&gt; &lt;span class="psCommand"&gt;New-Object&lt;/span&gt; &lt;span class="psCommandParameter"&gt;-ComObject&lt;/span&gt; &lt;span class="psString"&gt;"VirtualPC.Application"&lt;/span&gt;
&lt;span class="psVariable"&gt;$vm&lt;/span&gt; &lt;span class="psOperator"&gt;=&lt;/span&gt; &lt;span class="psVariable"&gt;$vpc&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;FindVirtualMachine&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psVariable"&gt;$VirtualMachineName&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;

&lt;span class="psKeyword"&gt;if&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt; &lt;span class="psVariable"&gt;$vm&lt;/span&gt; &lt;span class="psOperator"&gt;-eq&lt;/span&gt; &lt;span class="psVariable"&gt;$null&lt;/span&gt; &lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
&lt;span class="psGroupStart"&gt;{&lt;/span&gt;
    &lt;span class="psCommand"&gt;Write-Error&lt;/span&gt; &lt;span class="psString"&gt;"Virtual machine not found."&lt;/span&gt;
&lt;span class="psGroupEnd"&gt;}&lt;/span&gt;
&lt;span class="psKeyword"&gt;else&lt;/span&gt;
&lt;span class="psGroupStart"&gt;{&lt;/span&gt;
    &lt;span class="psKeyword"&gt;if&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt; &lt;span class="psVariable"&gt;$vm&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;FloppyDrives&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;Item&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psNumber"&gt;1&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;ImageFile&lt;/span&gt; &lt;span class="psOperator"&gt;-ne&lt;/span&gt; &lt;span class="psVariable"&gt;$null&lt;/span&gt; &lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
    &lt;span class="psGroupStart"&gt;{&lt;/span&gt;
        &lt;span class="psVariable"&gt;$vm&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;FloppyDrives&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;Item&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psNumber"&gt;1&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;ReleaseImage&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
    &lt;span class="psGroupEnd"&gt;}&lt;/span&gt;
    &lt;span class="psKeyword"&gt;elseif&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt; &lt;span class="psVariable"&gt;$vm&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;FloppyDrives&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;Item&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psNumber"&gt;1&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;HostDriveLetter&lt;/span&gt; &lt;span class="psOperator"&gt;-ne&lt;/span&gt; &lt;span class="psVariable"&gt;$null&lt;/span&gt; &lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
    &lt;span class="psGroupStart"&gt;{&lt;/span&gt;
        &lt;span class="psVariable"&gt;$vm&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;FloppyDrives&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;Item&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psNumber"&gt;1&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;&lt;span class="psOperator"&gt;.&lt;/span&gt;&lt;span class="psMember"&gt;ReleaseHostDrive&lt;/span&gt;&lt;span class="psGroupStart"&gt;(&lt;/span&gt;&lt;span class="psGroupEnd"&gt;)&lt;/span&gt;
    &lt;span class="psGroupEnd"&gt;}&lt;/span&gt;
    &lt;span class="psKeyword"&gt;else&lt;/span&gt;
    &lt;span class="psGroupStart"&gt;{&lt;/span&gt;
        &lt;span class="psCommand"&gt;Write-Warning&lt;/span&gt; &lt;span class="psString"&gt;"No virtual floppy is attached to the virtual machine $VirtualMachineName."&lt;/span&gt;
    &lt;span class="psGroupEnd"&gt;}&lt;/span&gt;
&lt;span class="psGroupEnd"&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Using the scripts is pretty simple. Run PowerShell, navigate to the directory where you put the scripts, and invoke them. Remember that you need to enable execution of unsigned scripts (&lt;code&gt;Set-ExecutionPolicy RemoteSigned&lt;/code&gt;), or sign the scripts.&lt;/p&gt;&lt;p&gt;The following example creates a virtual floppy drive image called sample.vfd, attaches it to the Windows XP Mode virtual machine, and then releases it again:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="psCommand"&gt;.\Create-VirtualFloppy.ps1&lt;/span&gt; &lt;span class="psCommandArgument"&gt;sample.vfd&lt;/span&gt;
&lt;span class="psCommand"&gt;.\Attach-VirtualFloppy.ps1&lt;/span&gt; &lt;span class="psString"&gt;"Windows XP Mode"&lt;/span&gt; &lt;span class="psCommandArgument"&gt;sample.vfd&lt;/span&gt;
&lt;span class="psCommand"&gt;.\Release-VirtualFloppy.ps1&lt;/span&gt; &lt;span class="psString"&gt;"Windows XP Mode"&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Please note that the virtual machine must be running for attach and release to work.&lt;/p&gt;&lt;p&gt;The following example attaches the host floppy drive A and then releases it again:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="psCommand"&gt;.\Attach-VirtualFloppy.ps1&lt;/span&gt; &lt;span class="psString"&gt;"Windows XP Mode"&lt;/span&gt; &lt;span class="psCommandArgument"&gt;A&lt;/span&gt; &lt;span class="psCommandParameter"&gt;-HostDrive&lt;/span&gt;
&lt;span class="psCommand"&gt;.\Release-VirtualFloppy.ps1&lt;/span&gt; &lt;span class="psString"&gt;"Windows XP Mode"&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;And that&amp;#39;s it! Now you can use floppy disks with Windows Virtual PC 7 once again.&lt;/p&gt;</description>
      <pubDate>Sun, 17 Oct 2010 06:20:03 Z</pubDate>
      <creator xmlns="http://purl.org/dc/elements/1.1/">Sven Groot</creator>
    </item>
  </channel>
</rss>