<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on Joel Ahlgren</title><link>https://www.ahlgren.io/posts/</link><description>Recent content in Posts on Joel Ahlgren</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.</copyright><lastBuildDate>Tue, 07 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.ahlgren.io/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Leaving Windows, After 30 Years</title><link>https://www.ahlgren.io/posts/2026/04/leaving-windows-after-30-years/</link><pubDate>Tue, 07 Apr 2026 00:00:00 +0000</pubDate><guid>https://www.ahlgren.io/posts/2026/04/leaving-windows-after-30-years/</guid><description>&lt;p&gt;For as long as I can recall, video games and computers have fascinated me. When I was about 8 years old my family purchased our first computer, a Pentium (1) running at 133 MHz with 8MB of RAM - and Windows 95.&lt;/p&gt;
&lt;p&gt;For much of my childhood, there was no concept of another operating system - Windows 95 was replaced with Windows 98, a friend at a LAN party got me to install Windows 2000 on my own computer. A neighbour&amp;rsquo;s dad tinkered around with Linux variants like Red Hat, but that only ever seemed like some abstract adventure and never a practical option - especially to a teenager who loved playing games on their computer.&lt;/p&gt;</description><content type="html"><![CDATA[<p>For as long as I can recall, video games and computers have fascinated me. When I was about 8 years old my family purchased our first computer, a Pentium (1) running at 133 MHz with 8MB of RAM - and Windows 95.</p>
<p>For much of my childhood, there was no concept of another operating system - Windows 95 was replaced with Windows 98, a friend at a LAN party got me to install Windows 2000 on my own computer. A neighbour&rsquo;s dad tinkered around with Linux variants like Red Hat, but that only ever seemed like some abstract adventure and never a practical option - especially to a teenager who loved playing games on their computer.</p>
<p>As the years passed, so did the versions of Windows I&rsquo;d used - Windows 2000 was replaced with Windows XP, which was surrendered to Windows Vista, then 7, 8.1, and 10.</p>
<h2 id="expanding-world-views">Expanding World Views</h2>
<p>I was pretty content with Windows 10, touted as &ldquo;The Last Version of Windows&rdquo;. It did what I needed it to, and I had no real reason to look elsewhere. Over the years, I had tried a few Debian/Ubuntu distros, but had usually given up after various issues with window managers and incompatibility with games.</p>
<p>I have had some variant of an Ubuntu server running, first as a VPS and later as a home server. But this has been entirely through terminal / SSH.</p>
<p>My job for the last 15 years has focused on building products targeting Windows, using mainly C#, so both at work and at home I&rsquo;ve been a consistent Windows user. But with Windows 11, I&rsquo;ve been continuously less impressed with Windows (and Microsoft at large). At work, I had a week of not being able to open the start menu as it crashed, due to some incompatible Group Policy combined with a Windows update, and many similar small issues.</p>
<p>I also switched teams/products at work and was issued a MacBook (but I still need my Windows laptop for certain product work), which has somewhat triggered a re-evaluation of Windows in my eyes. I will say that while the MacBook Pro (M4) hardware is very impressive - I&rsquo;m having a hard time getting into the software side of the Apple world.</p>
<h2 id="the-big-shift">The Big Shift</h2>
<p>As Microsoft moved deeper into its LLM/Copilot frenzy, I finally had enough and took a serious look around - and found that gaming under Linux really has shifted over the last few years. I found myself looking at either Bazzite or CachyOS, and ended up choosing CachyOS for my first serious foray into the Linux desktop in many years.</p>
<p>I went with a dual-boot setup to be able to return to Windows 11 if it turned out the Linux experience left me unable to do something - but nearly two months in I&rsquo;ve not booted into Windows once. I&rsquo;m very close to making a clean install of CachyOS on my bigger SSD (where Windows currently lies, waiting and nearly forgotten).</p>
<p>I&rsquo;ve now played through Resident Evil 9 and am halfway through Death Stranding 2 on CachyOS, and I&rsquo;ve had very few issues (some nVidia driver issues with RE9, and I had to find some custom launch arguments for DS2) - but they&rsquo;ve been very minor IMHO.</p>
<p>Due to my work, I&rsquo;ll still be touching Windows almost daily - but it&rsquo;s a strangely freeing feeling to know that I&rsquo;m in no way bound to Windows anymore.</p>
]]></content></item><item><title>The Importance of Frequent Releases</title><link>https://www.ahlgren.io/posts/2024/04/the-importance-of-frequent-releases/</link><pubDate>Mon, 29 Apr 2024 00:00:00 +0000</pubDate><guid>https://www.ahlgren.io/posts/2024/04/the-importance-of-frequent-releases/</guid><description>&lt;p&gt;When I started working at my current company, nearly 14 years ago now, we had a very waterfall aligned way of working. Back then I was working as a QA engineer, but my work was closely tied to the software release schedule of the software team.&lt;/p&gt;
&lt;p&gt;As we produce MedTech devices, they have various requirements on documentation and validation before a release is approved, this affects hardware more severely than software - but we handled development of both essentially as one.&lt;/p&gt;</description><content type="html"><![CDATA[<p>When I started working at my current company, nearly 14 years ago now, we had a very waterfall aligned way of working. Back then I was working as a QA engineer, but my work was closely tied to the software release schedule of the software team.</p>
<p>As we produce MedTech devices, they have various requirements on documentation and validation before a release is approved, this affects hardware more severely than software - but we handled development of both essentially as one.</p>
<p>This meant that many of our (Windows Desktop) software products saw updates released every <strong>12 to 18 months</strong>, unless there was a critical bug that needed to be fixed - which prompted a hotfix.</p>
<p>Fast-forwarding a few years, in 2019 we got the opportunity to do a fresh start for a project to replace an existing product. Of course this project was named <strong>Phoenix</strong> (as required by law). This coincided with a new Engineering Manager who had big plans for our software process, which meant assembling a cross-functional agile team that worked in shorter sprints and produced regular releases.</p>
<h2 id="new-times">New Times</h2>
<p>The team decided on two week long sprints, moved from on-premise systems running TeamCity with self-hosted agents and a big common library of MSBuild tasks to Azure DevOps and <a href="https://cakebuild.net/">Cake</a>. This allowed us several improvmements to our workflow - one of which being we could ensure our builds ran the same when developing locally, as well as when being built by our CI system.</p>
<p>But the greatest gain was the ambition (and support, at least from our nearest manager) to push a release with every sprint. This meant going from <strong>0.75 - 1.0</strong> releases per year to a little <strong>over 20</strong> releases per year.</p>
<p>This shortened our feedback loops with our users (and the people who set up and sold systems to our users), giving good focus to the <em>R</em> in <em>R&amp;D</em>. It also meant that we could react much quicker to fix bugs, even when they were not critical.</p>
<p>We released our MVP version to Beta users after a (for us) short 11 sprints, and then version 1.0 to general availability after 17 sprints - and very quickly our users got used to the quick release cadence. We frequently got feedback on new features being tested, and issues that were found were quickly fixed.</p>
<h2 id="the-biggest-changes">The Biggest Changes</h2>
<p>The big things we made in order to enable fast and frequent releases were:</p>
<h4 id="local-and-ci-builds">Local and CI Builds</h4>
<p>As previously mentioned, we leveraged Cake to orchestrate our builds: compiling binaries and installers, running unit tests and code coverage, static code analysis, collecting content and packaging for release, etc. We set this up in a way that when you run the build locally it will do the same operations as when run on CI. This allowed us to be more confident that when we push something to the server, it&rsquo;s going to build in the same way.</p>
<p>Of course there are some differences we cannot escape, like the CI builds will be signed with a live code signing certificate, while the local builds are signed with a local dummy certificate, etc. We also block any deployments to cloud resources except for when the build runs in CI. But the main part of the build process will behave exactly the same in both environments.</p>
<h4 id="version-control-discipline">Version Control Discipline</h4>
<p>We agreed that any merge into the main branch needed to be &ldquo;releasable&rdquo;, meaning first the obvious things like it had to compile, the unit tests needed to pass, etc. But it also meant that there shouldn&rsquo;t be any half-finished features or known (critical) bugs in the merge.</p>
<p>This was partly handled by having automated checks on all Pull Requests, but also by the team enforcing this belief in discussions and code reviews.</p>
<p>Having a main branch that is always releasable means that if something bad is deployed, it&rsquo;s easy to go back (or forward) to a good state again.</p>
<h4 id="feature-toggles">Feature Toggles</h4>
<p>Something we&rsquo;d never used within our department before, was the concept of <a href="https://en.wikipedia.org/wiki/Feature_toggle/">Feature Toggles/Switches</a>. Using these allowed us to work on large features that would not necessarily be ready for release, and still merge the code to our main branch without actually affecting users. Our first implementation had a fairly complex setup of sending out feature configs dynamically to clients (along with bundling configs with updates), but after some time we realized that we spent a lot of time maintaining this system and rarely used it.</p>
<p>In the end, we removed the ability to change enabled features remotely, and are just defining the features at compile time (but typically different features are enabled for different installation &ldquo;tracks&rdquo;).</p>
<h4 id="synchronizing-with-the-others">Synchronizing With the Others</h4>
<p>Probably the biggest pain point was to synchronize this effort with other parts of the organization, which was used to / built around very infrequent releases.</p>
<p>We had to find new contracts with our localization partners, as we now were requesting translations several times a month, rather than 1-3 times per year.</p>
<p>We had to produce regression test reports every sprint, and we had to distil our (manual) regression tests to be more efficient, as they needed to be executed by someone on the team every two weeks.</p>
<p>We tried finding ways around other things to avoid causing overloads on other departments, like Tech Writing who did not appreciate having to update our user manual every two weeks (in the end, they didn&rsquo;t, and today we update the manuals at most every quarter if something significant has changed or been added).</p>
<h2 id="post-mortem-5-years-later">Post-Mortem, 5 years later</h2>
<p>After over 80 releases to Production, the product which was Phoenix has entered into a temporary (?) state of &ldquo;maintenance&rdquo; - meaning we don&rsquo;t work actively on it right now, and only do releases for reported bug fixes. My team was instead assigned to another product in the same &ldquo;suite&rdquo; of products, and ended up rewriting it almost entirely from scratch.</p>
<p>We&rsquo;ve followed essentially the same strategy as we did 5 years earlier, with some tweaks picked up along the way. Over the last year there have been several initiatives across several departments to coordinate better around releases. It has meant that we&rsquo;ve more frequently needed to use feature toggles to hold back features from General Availability, which is working OK for us - but sometimes it feels odd holding back complete features for several months.</p>
<p>As of late we&rsquo;ve had parts of the company pushing for essentially a more waterfall-like approach of long-term planning and doing only a few releases per year. We&rsquo;re trying to reach some compromise which will let us keep up our frequent release schedule, as we believe it&rsquo;s the best way to deliver continuous value to our users.</p>
]]></content></item><item><title>The Germans and Sailor Moon</title><link>https://www.ahlgren.io/posts/2024/04/the-germans-and-sailor-moon/</link><pubDate>Sun, 28 Apr 2024 00:00:00 +0000</pubDate><guid>https://www.ahlgren.io/posts/2024/04/the-germans-and-sailor-moon/</guid><description>&lt;p&gt;I&amp;rsquo;m getting a little side tracked here, but I really need to write this out. What&amp;rsquo;s up with Germans and foreign multimedia?&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know why I know this, but at some point I became aware and this knowledge haunts me. There are several approaches to localization when it comes to foreign media: original audio and subtitles, dubbing the dialogue in your language, keeping text and signage in the original language or drawing localized text on top of the orginal video, etc.&lt;/p&gt;</description><content type="html"><![CDATA[<p>I&rsquo;m getting a little side tracked here, but I really need to write this out. What&rsquo;s up with Germans and foreign multimedia?</p>
<p>I don&rsquo;t know why I know this, but at some point I became aware and this knowledge haunts me. There are several approaches to localization when it comes to foreign media: original audio and subtitles, dubbing the dialogue in your language, keeping text and signage in the original language or drawing localized text on top of the orginal video, etc.</p>
<p>For <a href="https://en.wikipedia.org/wiki/Sailor_Moon">Sailor Moon</a>, originally a japanese animated TV-series (based on a manga series) there are many approaches. I&rsquo;ll focus on the first opening sequence of the show, which has been localized in several different ways.</p>
<h2 id="the-original-japanese-opening">The original Japanese opening</h2>
<p>As a baseline for this comparison, below is a YouTube video showing the original opening sequence:
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/kg-McDlm_98?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>
</p>
<h2 id="more-reasonable-localizations">More reasonable localizations</h2>
<p>The Swedish version has a fairly faithful translation of the Japanese song, and keeps the original video as well - but gets a little creative by hiding the japanese text with some credits and then displays the title afterwards.
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/j2tQKejS9Fo?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>
</p>
<p>The (US?) english localization has a more action-oriented feel to its visuals, and actually changes the song - but still keeps &ldquo;the feeling&rdquo; of the original opening.
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/dOsuVJEY_No?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>
</p>
<h2 id="the-german-localization">The German localization</h2>
<p>Then there&rsquo;s the German approach&hellip;</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/DnkxOqMfxt0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Here we also have much of the original video, but the Japanese title cards are hidden by showing some other &ldquo;B footage&rdquo; instead, afterwards returning to essentially the original video again.</p>
<p>But the song has been entirely replaced with a very upbeat techno-esque song. Don&rsquo;t get me wrong, it&rsquo;s a bop, but it sure changes the experience from the original.</p>
]]></content></item><item><title>Abandoning An Open Source Project</title><link>https://www.ahlgren.io/posts/2024/04/abandoning-an-open-source-project/</link><pubDate>Wed, 10 Apr 2024 00:00:00 +0000</pubDate><guid>https://www.ahlgren.io/posts/2024/04/abandoning-an-open-source-project/</guid><description>&lt;h3 id="background"&gt;Background&lt;/h3&gt;
&lt;p&gt;Back in May of 2018 I made a &lt;a href="https://github.com/squid-box/SevenZipSharp"&gt;fork&lt;/a&gt; of a &lt;a href="https://github.com/tomap/SevenZipSharp"&gt;fork&lt;/a&gt; of SevenZipSharp (originally found at &lt;a href="https://sevenzipsharp.codeplex.com/"&gt;https://sevenzipsharp.codeplex.com/&lt;/a&gt; ) - a library which exposed 7zip within C#/.NET. I did so because another project I was contributing to was using this library to handle archived files, and needed to add support for RAR5 archives - but the maintainer of the fork we used (essentially the most popular fork at the time) had little interest in maintaining his fork.&lt;/p&gt;</description><content type="html"><![CDATA[<h3 id="background">Background</h3>
<p>Back in May of 2018 I made a <a href="https://github.com/squid-box/SevenZipSharp">fork</a> of a <a href="https://github.com/tomap/SevenZipSharp">fork</a> of SevenZipSharp (originally found at <a href="https://sevenzipsharp.codeplex.com/">https://sevenzipsharp.codeplex.com/</a> ) - a library which exposed 7zip within C#/.NET. I did so because another project I was contributing to was using this library to handle archived files, and needed to add support for RAR5 archives - but the maintainer of the fork we used (essentially the most popular fork at the time) had little interest in maintaining his fork.</p>
<p>When he suggested I create a fork of his repo and start comitting to it, and then he would reference to my fork from his repo before he archived it, I agreed.</p>
<p>Over the years I made several improvements of my own, and got a fair bit of contributions from other GitHub users as well. It&rsquo;s mildly used across various projects, at the time of writing the <a href="https://www.nuget.org/packages/Squid-Box.SevenZipSharp">NuGet</a> has nearly 475k downloads (which still surprises me).</p>
<h3 id="active-work">&ldquo;Active&rdquo; work</h3>
<p>From converting the unit tests to something that can be run in CI, to actually setting up CI/CD, to fixing bugs and extending format support - I&rsquo;ve done a lot of things, and even if I&rsquo;ve technically learned quite a bit about file compression, archives, and formats - I&rsquo;ve essentially just poked at a system I didn&rsquo;t quite understand until it did what I thought was right.</p>
<p>Overall it&rsquo;s been a very pleasant experience, most people have been courteous when reporting issues - responding to my questions and testing my attempted fixes. And especially the incoming contributions from others have been a fun experience - even if the experience of code reviewing someone elses changes into a system I don&rsquo;t fully grasp has proven stressful at times. I&rsquo;ve included almost all work suggested/requested, with few exceptions where I&rsquo;ve not gotten good enough reasoning around changes made.</p>
<h3 id="decline">Decline</h3>
<p>Once the novelty of a new (pre-owned?) project had worn off, and the features required by the other project I was contributing to were implemented - it became harder to motivate myself to work on the project. Over the 6 years I&rsquo;ve taken care of this fork I&rsquo;ve had several hiatuses, some stretching a full year, where I didn&rsquo;t do any work on the project. Sometimes I&rsquo;d look through the reported issues on GitHub, reply to some I knew the answers to - and in some instances actually sit down to try and reproduce reported issues in order to find a fix to the problem.</p>
<p>At some point the project became a source of stress (or shame?), where I felt like I <em>should</em> work on it but couldn&rsquo;t find the motivation to. A year ago I decided pretty firmly that I would make some final changes and then archive the project - but &hellip; that never happened. 🤷‍♂️</p>
<p>As of writing this post I&rsquo;ve just pushed the last version I intend to create, 1.6.2, to NuGet.org and archived the project on GitHub.</p>
<p><img src="/img/leaving-sevenzipsharp/archived.png#center" alt="Archived GitHub project"></p>
<p>While the action of archiving a project is in no way final or destructive, it does feel like packing it up and putting it away in the attic.</p>
]]></content></item><item><title>Running Windows Processes Across Sessions</title><link>https://www.ahlgren.io/posts/2023/02/running-windows-processes-across-sessions/</link><pubDate>Wed, 22 Feb 2023 00:00:00 +0000</pubDate><guid>https://www.ahlgren.io/posts/2023/02/running-windows-processes-across-sessions/</guid><description>&lt;p&gt;At work I encountered a situation where I had to start a Windows process in the active user session, from a system service running under the Local System account. Since it was hard to find a collected explanation on how to do this, I&amp;rsquo;ve decided to document it here.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The product I am mainly working on is a Windows Desktop application, which consists of two components, installed as separate products: the application itself, and a system service used to provide updates to the application. The system service runs in the background, polling a server for new updates. When the user starts the application, if there is an available update, the system service will update the application and then launch the new version.&lt;/p&gt;</description><content type="html"><![CDATA[<p>At work I encountered a situation where I had to start a Windows process in the active user session, from a system service running under the Local System account. Since it was hard to find a collected explanation on how to do this, I&rsquo;ve decided to document it here.</p>
<h2 id="the-problem">The Problem</h2>
<p>The product I am mainly working on is a Windows Desktop application, which consists of two components, installed as separate products: the application itself, and a system service used to provide updates to the application. The system service runs in the background, polling a server for new updates. When the user starts the application, if there is an available update, the system service will update the application and then launch the new version.</p>
<p>The updates of the application come in two variants:</p>
<ol>
<li>Simple file replacement update, to reduce update file size and update time.</li>
<li>Full MSI installer upgrade, used when necessary.</li>
</ol>
<p>In the first case the update is applied while the application is partly running and can launch the application once done.</p>
<p>The second case is trickier, as the application needs to be fully shut down before applying the MSI upgrade. The MSI does have logic for starting the application once the installation completes, but since it&rsquo;s launched by the system service it runs in <a href="https://techcommunity.microsoft.com/t5/ask-the-performance-team/application-compatibility-session-0-isolation/ba-p/372361">Session 0</a> and is unable to launch the application in the active user session.</p>
<h2 id="the-solution">The Solution</h2>
<p>As with all tricky problems, there is an equally tricky solution - in this case using <a href="https://docs.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke">P/Invoke</a> to leverage low-level Windows functionality is the right tool for the job.</p>
<p>I am going to be referencing various functions here, most of which have good documentation and examples at <a href="https://www.pinvoke.net/">PInvoke.net</a>.</p>
<p>The solution is to copy a token from a running process in the active session and use it to launch our own process in that same session.</p>
<h3 id="acquiring-a-user-access-token">Acquiring a user access token</h3>
<p>The first thing we need to do is to find which session is the currently active one, for this we use <code>WTSGetActiveConsoleSessionId</code> to find the session ID. Using this session ID, we look for a process running in that session. We then duplicate the user access token of this process.</p>
<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">var</span> knownProcessId = <span style="color:#3677a9">0</span>;
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">var</span> userAccessToken = IntPtr.Zero;
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">var</span> processTokenHandle = IntPtr.Zero;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#999;font-style:italic">// We first need to find the active user session ID.</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">var</span> activeConsoleSessionId = WTSGetActiveConsoleSessionId();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#999;font-style:italic">// Then we need to find the a process running in that session, explorer is typically safe.</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">foreach</span> (<span style="color:#6ab825;font-weight:bold">var</span> process <span style="color:#6ab825;font-weight:bold">in</span> Process.GetProcessesByName(<span style="color:#ed9d13">&#34;explorer&#34;</span>))
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">if</span> ((<span style="color:#6ab825;font-weight:bold">uint</span>)process.SessionId == activeConsoleSessionId)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        knownProcessId = process.Id;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#999;font-style:italic">// Obtain a handle to the known process.</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">var</span> processHandle = OpenProcess(ProcessAccessFlagsMaximumAllowed, <span style="color:#6ab825;font-weight:bold">false</span>, knownProcessId);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#999;font-style:italic">// Obtain a handle to the access token of the known process.</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">if</span> (!OpenProcessToken(processHandle, (<span style="color:#6ab825;font-weight:bold">uint</span>)TokenAccessLevels.Duplicate, <span style="color:#6ab825;font-weight:bold">ref</span> processTokenHandle))
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    Console.WriteLine(<span style="color:#ed9d13">$&#34;Unable to obtain handle for known process, error code: {Marshal.GetLastWin32Error()}&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    CloseHandle(processHandle);
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">return</span> <span style="color:#6ab825;font-weight:bold">false</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#999;font-style:italic">// Copy the access token of the known executable process. The newly created token will be a primary token.</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">var</span> securityAttributes = <span style="color:#6ab825;font-weight:bold">default</span>(SECURITY_ATTRIBUTES);
</span></span><span style="display:flex;"><span>securityAttributes.Length = Marshal.SizeOf(securityAttributes);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">var</span> duplicationResults = DuplicateTokenEx(processTokenHandle, (<span style="color:#6ab825;font-weight:bold">uint</span>)TokenAccessLevels.MaximumAllowed, <span style="color:#6ab825;font-weight:bold">ref</span> securityAttributes, SecurityIdentification, TokenTypeTokenPrimary, <span style="color:#6ab825;font-weight:bold">ref</span> userAccessToken);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>CloseHandle(processHandle);
</span></span><span style="display:flex;"><span>CloseHandle(processTokenHandle);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">if</span> (!duplicationResults)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    Console.WriteLine(<span style="color:#ed9d13">$&#34;Unable to duplicate process token, error code: {Marshal.GetLastWin32Error()}&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">return</span> <span style="color:#6ab825;font-weight:bold">false</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="modifying-the-duplicated-user-access-token">Modifying the duplicated user access token</h3>
<p>As icing on the cake, the application I need to start also requires UIAccess, so we need to set that flag as well on the token using <code>SetTokenInformation</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">var</span> tokenInformation = <span style="color:#3677a9">1</span>;
</span></span><span style="display:flex;"><span>SetTokenInformation(userAccessToken, TokenUiAccess, <span style="color:#6ab825;font-weight:bold">ref</span> tokenInformation, Marshal.SizeOf(tokenInformation));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">var</span> setTokenResult = Marshal.GetLastWin32Error();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">if</span> (setTokenResult != <span style="color:#3677a9">0</span>)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    Console.WriteLine(<span style="color:#ed9d13">$&#34;Unable to set UI access, error code: {setTokenResult}&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    CloseHandle(userAccessToken);
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">return</span> <span style="color:#6ab825;font-weight:bold">false</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Note that for this to work, the application you are duplicating a token from also needs to have UIAccess permissions or this won&rsquo;t work (meaning that <code>explorer</code> used above will not suffice. Luckily for me I had another process I could depend on for this.</p>
<h3 id="duplicating-the-user-environment">Duplicating the user environment</h3>
<p>Depending on what process you&rsquo;re trying to start, you might want to ensure that you have access to the same environment variables as the process you&rsquo;re copying a token from, e.g., if you&rsquo;re using %APPDATA%.</p>
<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">if</span> (!CreateEnvironmentBlock(<span style="color:#6ab825;font-weight:bold">out</span> <span style="color:#6ab825;font-weight:bold">var</span> environmentBlock, userAccessToken, <span style="color:#6ab825;font-weight:bold">true</span>))
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    Console.WriteLine(<span style="color:#ed9d13">$&#34;Unable to duplicate environment block, error code: {Marshal.GetLastWin32Error()}&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    CloseHandle(userAccessToken);
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">return</span> <span style="color:#6ab825;font-weight:bold">false</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Once you have launched your process, you also need to ensure you&rsquo;re disposing of this duplicated environment:</p>
<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>DestroyEnvironmentBlock(environmentBlock);
</span></span></code></pre></div><h3 id="starting-the-application-in-the-active-session">Starting the application in the active session</h3>
<p>Then we just need to set up the final parts and we can launch the process</p>
<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">var</span> startupInfo = <span style="color:#6ab825;font-weight:bold">default</span>(STARTUP_INFO);
</span></span><span style="display:flex;"><span>startupInfo.cb = Marshal.SizeOf(startupInfo);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#999;font-style:italic">// This indicates that the process created can display a GUI on the desktop.</span>
</span></span><span style="display:flex;"><span>startupInfo.lpDesktop = <span style="color:#ed9d13">@&#34;winsta0\default&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">const</span> <span style="color:#6ab825;font-weight:bold">uint</span> creationFlags = (<span style="color:#6ab825;font-weight:bold">uint</span>)ProcessPriorityClass.NORMAL_PRIORITY_CLASS | (<span style="color:#6ab825;font-weight:bold">uint</span>)ProcessCreationFlags.CREATE_NEW_CONSOLE;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#999;font-style:italic">// Create a new process in the current user&#39;s active session.</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">var</span> result = CreateProcessWithTokenW(userAccessToken, LogonFlagWithProfile, <span style="color:#6ab825;font-weight:bold">null</span>!, executablePathAndArgs, (CreationFlagNewConsole | CreationFlagUnicodeEnvironment), environmentBlock, workingDirectory, <span style="color:#6ab825;font-weight:bold">ref</span> startupInfo, <span style="color:#6ab825;font-weight:bold">out</span> _);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">if</span> (!result)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    Console.WriteLine(<span style="color:#ed9d13">$&#34;CreateProcessWithTokenW returned {Marshal.GetLastWin32Error()}&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>CloseHandle(userAccessToken);
</span></span></code></pre></div><h2 id="thoughts">Thoughts</h2>
<p>This was not the only explored solution to this problem and did not end up being the solution we went with - but I still thought it was interesting enough that I wanted to document it here.</p>
<p>While I often try to avoid resorting to P/Invokes, this solution is fairly robust (but make sure to destroy and release everything) - it makes no crazy assumptions and should work on any Windows version since Vista.</p>
<h2 id="final-result">Final result</h2>
<p>Here is the full snippet of code to be able to launch a process in another user session:</p>
<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#cd2828;font-weight:bold">#region</span> P/Invokes
</span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">[DllImport(&#34;kernel32.dll&#34;)]</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">static</span> <span style="color:#6ab825;font-weight:bold">extern</span> <span style="color:#6ab825;font-weight:bold">uint</span> WTSGetActiveConsoleSessionId();
</span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">[DllImport(&#34;kernel32.dll&#34;)]</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">static</span> <span style="color:#6ab825;font-weight:bold">extern</span> IntPtr OpenProcess(<span style="color:#6ab825;font-weight:bold">uint</span> dwDesiredAccess, <span style="color:#6ab825;font-weight:bold">bool</span> bInheritHandle, <span style="color:#6ab825;font-weight:bold">int</span> dwProcessId);
</span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">[DllImport(&#34;advapi32.dll&#34;, SetLastError = true), SuppressUnmanagedCodeSecurity]</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">static</span> <span style="color:#6ab825;font-weight:bold">extern</span> <span style="color:#6ab825;font-weight:bold">bool</span> OpenProcessToken(IntPtr ProcessHandle, <span style="color:#6ab825;font-weight:bold">uint</span> DesiredAccess, <span style="color:#6ab825;font-weight:bold">ref</span> IntPtr TokenHandle);
</span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">[DllImport(&#34;kernel32.dll&#34;, SetLastError = true)]</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">static</span> <span style="color:#6ab825;font-weight:bold">extern</span> <span style="color:#6ab825;font-weight:bold">bool</span> CloseHandle(IntPtr hHandle);
</span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">[DllImport(&#34;advapi32.dll&#34;, EntryPoint = &#34;DuplicateTokenEx&#34;)]</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">static</span> <span style="color:#6ab825;font-weight:bold">extern</span> <span style="color:#6ab825;font-weight:bold">bool</span> DuplicateTokenEx(
</span></span><span style="display:flex;"><span>    IntPtr hExistingToken,
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">uint</span> dwDesiredAccess,
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">ref</span> SECURITY_ATTRIBUTES lpTokenAttributes,
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">int</span> ImpersonationLevel,
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">int</span> TokenType,
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">ref</span> IntPtr phNewToken);
</span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">[DllImport(&#34;advapi32.dll&#34;, SetLastError = true)]</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">static</span> <span style="color:#6ab825;font-weight:bold">extern</span> <span style="color:#6ab825;font-weight:bold">bool</span> SetTokenInformation(IntPtr TokenHandle, <span style="color:#6ab825;font-weight:bold">int</span> TokenInformationClass, <span style="color:#6ab825;font-weight:bold">ref</span> <span style="color:#6ab825;font-weight:bold">int</span> TokenInformation, <span style="color:#6ab825;font-weight:bold">int</span> TokenInformationLength);
</span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">[DllImport(&#34;advapi32&#34;, SetLastError = true, CharSet = CharSet.Unicode)]</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">static</span> <span style="color:#6ab825;font-weight:bold">extern</span> <span style="color:#6ab825;font-weight:bold">bool</span> CreateProcessWithTokenW(
</span></span><span style="display:flex;"><span>    IntPtr hToken, 
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">int</span> dwLogonFlags, 
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">string</span> lpApplicationName, 
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">string</span> lpCommandLine, 
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">int</span> dwCreationFlags, 
</span></span><span style="display:flex;"><span>    IntPtr lpEnvironment, 
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">string</span> lpCurrentDirectory, 
</span></span><span style="display:flex;"><span><span style="color:#bbb">    [In]</span> <span style="color:#6ab825;font-weight:bold">ref</span> STARTUP_INFO lpStartupInfo, 
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">out</span> PROCESS_INFORMATION lpProcessInformation);
</span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">[DllImport(&#34;userenv.dll&#34;, SetLastError = true)]</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">static</span> <span style="color:#6ab825;font-weight:bold">extern</span> <span style="color:#6ab825;font-weight:bold">bool</span> CreateEnvironmentBlock(<span style="color:#6ab825;font-weight:bold">out</span> IntPtr lpEnvironment, IntPtr hToken, <span style="color:#6ab825;font-weight:bold">bool</span> bInherit);
</span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">[DllImport(&#34;userenv.dll&#34;, SetLastError = true)]</span>
</span></span><span style="display:flex;"><span><span style="color:#bbb">[return: MarshalAs(UnmanagedType.Bool)]</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">static</span> <span style="color:#6ab825;font-weight:bold">extern</span> <span style="color:#6ab825;font-weight:bold">bool</span> DestroyEnvironmentBlock(IntPtr lpEnvironment);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cd2828;font-weight:bold">#region</span> Structs
</span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">[StructLayout(LayoutKind.Sequential)]</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">struct</span> <span style="color:#447fcf;text-decoration:underline">SECURITY_ATTRIBUTES</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">int</span> Length;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> IntPtr lpSecurityDescriptor;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">bool</span> bInheritHandle;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">[StructLayout(LayoutKind.Sequential)]</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">struct</span> <span style="color:#447fcf;text-decoration:underline">STARTUP_INFO</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">int</span> cb;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">string</span> lpReserved;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">string</span> lpDesktop;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">string</span> lpTitle;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">uint</span> dwX;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">uint</span> dwY;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">uint</span> dwXSize;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">uint</span> dwYSize;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">uint</span> dwXCountChars;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">uint</span> dwYCountChars;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">uint</span> dwFillAttribute;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">uint</span> dwFlags;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">short</span> wShowWindow;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">short</span> cbReserved2;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> IntPtr lpReserved2;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> IntPtr hStdInput;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> IntPtr hStdOutput;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> IntPtr hStdError;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span><span style="color:#bbb">[StructLayout(LayoutKind.Sequential)]</span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">struct</span> <span style="color:#447fcf;text-decoration:underline">PROCESS_INFORMATION</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> IntPtr hProcess;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> IntPtr hThread;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">uint</span> dwProcessId;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">uint</span> dwThreadId;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cd2828;font-weight:bold">#endregion</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cd2828;font-weight:bold">#region</span> Constants
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">private</span> <span style="color:#6ab825;font-weight:bold">const</span> <span style="color:#6ab825;font-weight:bold">uint</span> ProcessAccessFlagsMaximumAllowed = <span style="color:#3677a9">0x2000000</span>;
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">private</span> <span style="color:#6ab825;font-weight:bold">const</span> <span style="color:#6ab825;font-weight:bold">int</span> SecurityIdentification = <span style="color:#3677a9">1</span>;
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">private</span> <span style="color:#6ab825;font-weight:bold">const</span> <span style="color:#6ab825;font-weight:bold">int</span> TokenTypeTokenPrimary = <span style="color:#3677a9">1</span>;
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">private</span> <span style="color:#6ab825;font-weight:bold">const</span> <span style="color:#6ab825;font-weight:bold">int</span> TokenUiAccess = <span style="color:#3677a9">26</span>;
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">private</span> <span style="color:#6ab825;font-weight:bold">const</span> <span style="color:#6ab825;font-weight:bold">int</span> LogonFlagWithProfile = <span style="color:#3677a9">1</span>;
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">private</span> <span style="color:#6ab825;font-weight:bold">const</span> <span style="color:#6ab825;font-weight:bold">int</span> CreationFlagNewConsole = <span style="color:#3677a9">0x00000010</span>;
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">private</span> <span style="color:#6ab825;font-weight:bold">const</span> <span style="color:#6ab825;font-weight:bold">int</span> CreationFlagUnicodeEnvironment = <span style="color:#3677a9">0x00000400</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cd2828;font-weight:bold">#endregion</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cd2828;font-weight:bold">#endregion</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6ab825;font-weight:bold">public</span> <span style="color:#6ab825;font-weight:bold">static</span> <span style="color:#6ab825;font-weight:bold">bool</span> LaunchProcessInUserSession(<span style="color:#6ab825;font-weight:bold">string</span> executablePathAndArgs, <span style="color:#6ab825;font-weight:bold">string</span> workingDirectory, <span style="color:#6ab825;font-weight:bold">bool</span> requireUiAccess)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">var</span> knownProcessId = <span style="color:#3677a9">0</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">var</span> userAccessToken = IntPtr.Zero;
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">var</span> processTokenHandle = IntPtr.Zero;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#999;font-style:italic">// We first need to find the active user session ID.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">var</span> activeConsoleSessionId = WTSGetActiveConsoleSessionId();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#999;font-style:italic">// Then we need to find the a process running in that session, explorer is typically safe.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">foreach</span> (<span style="color:#6ab825;font-weight:bold">var</span> process <span style="color:#6ab825;font-weight:bold">in</span> Process.GetProcessesByName(<span style="color:#ed9d13">&#34;explorer&#34;</span>))
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#6ab825;font-weight:bold">if</span> ((<span style="color:#6ab825;font-weight:bold">uint</span>)process.SessionId == activeConsoleSessionId)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            knownProcessId = process.Id;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#999;font-style:italic">// Obtain a handle to the known process.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">var</span> processHandle = OpenProcess(ProcessAccessFlagsMaximumAllowed, <span style="color:#6ab825;font-weight:bold">false</span>, knownProcessId);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#999;font-style:italic">// Obtain a handle to the access token of the known process.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">if</span> (!OpenProcessToken(processHandle, (<span style="color:#6ab825;font-weight:bold">uint</span>)TokenAccessLevels.Duplicate, <span style="color:#6ab825;font-weight:bold">ref</span> processTokenHandle))
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        Console.WriteLine(<span style="color:#ed9d13">$&#34;Unable to obtain handle for known process, error code: {Marshal.GetLastWin32Error()}&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        CloseHandle(processHandle);
</span></span><span style="display:flex;"><span>        <span style="color:#6ab825;font-weight:bold">return</span> <span style="color:#6ab825;font-weight:bold">false</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#999;font-style:italic">// Copy the access token of the known executable process. The newly created token will be a primary token.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">var</span> securityAttributes = <span style="color:#6ab825;font-weight:bold">default</span>(SECURITY_ATTRIBUTES);
</span></span><span style="display:flex;"><span>    securityAttributes.Length = Marshal.SizeOf(securityAttributes);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">var</span> duplicationResults = DuplicateTokenEx(processTokenHandle, (<span style="color:#6ab825;font-weight:bold">uint</span>)TokenAccessLevels.MaximumAllowed, <span style="color:#6ab825;font-weight:bold">ref</span> securityAttributes, SecurityIdentification, TokenTypeTokenPrimary, <span style="color:#6ab825;font-weight:bold">ref</span> userAccessToken);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    CloseHandle(processHandle);
</span></span><span style="display:flex;"><span>    CloseHandle(processTokenHandle);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">if</span> (!duplicationResults)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        Console.WriteLine(<span style="color:#ed9d13">$&#34;Unable to duplicate process token, error code: {Marshal.GetLastWin32Error()}&#34;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#6ab825;font-weight:bold">return</span> <span style="color:#6ab825;font-weight:bold">false</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">var</span> startupInfo = <span style="color:#6ab825;font-weight:bold">default</span>(STARTUP_INFO);
</span></span><span style="display:flex;"><span>    startupInfo.cb = Marshal.SizeOf(startupInfo);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#999;font-style:italic">// This indicates that the process created can display a GUI on the desktop.</span>
</span></span><span style="display:flex;"><span>    startupInfo.lpDesktop = <span style="color:#ed9d13">@&#34;winsta0\default&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">if</span> (requireUiAccess)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#6ab825;font-weight:bold">var</span> tokenInformation = <span style="color:#3677a9">1</span>;
</span></span><span style="display:flex;"><span>        SetTokenInformation(userAccessToken, TokenUiAccess, <span style="color:#6ab825;font-weight:bold">ref</span> tokenInformation, Marshal.SizeOf(tokenInformation));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#6ab825;font-weight:bold">var</span> setTokenResult = Marshal.GetLastWin32Error();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#6ab825;font-weight:bold">if</span> (setTokenResult != <span style="color:#3677a9">0</span>)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            Console.WriteLine(<span style="color:#ed9d13">$&#34;Unable to set UI access, error code: {setTokenResult}&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            CloseHandle(userAccessToken);
</span></span><span style="display:flex;"><span>            <span style="color:#6ab825;font-weight:bold">return</span> <span style="color:#6ab825;font-weight:bold">false</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">if</span> (!CreateEnvironmentBlock(<span style="color:#6ab825;font-weight:bold">out</span> <span style="color:#6ab825;font-weight:bold">var</span> environmentBlock, userAccessToken, <span style="color:#6ab825;font-weight:bold">true</span>))
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        Console.WriteLine(<span style="color:#ed9d13">$&#34;Unable to duplicate environment block, error code: {Marshal.GetLastWin32Error()}&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        CloseHandle(userAccessToken);
</span></span><span style="display:flex;"><span>        <span style="color:#6ab825;font-weight:bold">return</span> <span style="color:#6ab825;font-weight:bold">false</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">var</span> result = CreateProcessWithTokenW(userAccessToken, LogonFlagWithProfile, <span style="color:#6ab825;font-weight:bold">null</span>!, executablePathAndArgs, (CreationFlagNewConsole | CreationFlagUnicodeEnvironment), environmentBlock, workingDirectory, <span style="color:#6ab825;font-weight:bold">ref</span> startupInfo, <span style="color:#6ab825;font-weight:bold">out</span> _);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">if</span> (!result)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        Console.WriteLine(<span style="color:#ed9d13">$&#34;CreateProcessWithTokenW returned {Marshal.GetLastWin32Error()}&#34;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    DestroyEnvironmentBlock(environmentBlock);
</span></span><span style="display:flex;"><span>    CloseHandle(userAccessToken);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6ab825;font-weight:bold">return</span> result;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div>]]></content></item><item><title>Hello World</title><link>https://www.ahlgren.io/posts/2022/10/hello-world/</link><pubDate>Thu, 13 Oct 2022 00:00:00 +0000</pubDate><guid>https://www.ahlgren.io/posts/2022/10/hello-world/</guid><description>&lt;p&gt;I&amp;rsquo;ll try to post about interesting things I come across either during work, or when working on hobby projects.&lt;/p&gt;
&lt;p&gt;This first post will be a short outline of how I set up the website. The site is generated with &lt;a href="https://gohugo.io"&gt;Hugo&lt;/a&gt; using the &lt;a href="https://github.com/rhazdon/hugo-theme-hello-friend-ng"&gt;Hello Friend NG&lt;/a&gt; theme, hosted compiled on my own &lt;a href="https://gitlab.ahlgren.io"&gt;GitLab instance&lt;/a&gt; and uploaded automatically to my own nginx webserver.&lt;/p&gt;
&lt;p&gt;Using GitLab&amp;rsquo;s CI/CD system, the site is built and deployed on each commit pushed to the main branch. I&amp;rsquo;m impressed that it takes about 10 seconds from pushing a commit until the site is updated.&lt;/p&gt;</description><content type="html"><![CDATA[<p>I&rsquo;ll try to post about interesting things I come across either during work, or when working on hobby projects.</p>
<p>This first post will be a short outline of how I set up the website. The site is generated with <a href="https://gohugo.io">Hugo</a> using the <a href="https://github.com/rhazdon/hugo-theme-hello-friend-ng">Hello Friend NG</a> theme, hosted compiled on my own <a href="https://gitlab.ahlgren.io">GitLab instance</a> and uploaded automatically to my own nginx webserver.</p>
<p>Using GitLab&rsquo;s CI/CD system, the site is built and deployed on each commit pushed to the main branch. I&rsquo;m impressed that it takes about 10 seconds from pushing a commit until the site is updated.</p>
]]></content></item></channel></rss>