<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ brooklyn - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ brooklyn - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 22:23:57 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/brkln/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Free Up and Automatically Manage Disk Space for WSL on Windows 10/11 ]]>
                </title>
                <description>
                    <![CDATA[ Windows Subsystem for Linux (WSL) lets you run a Linux environment directly on Windows. This is particularly useful for web development where you can develop and test applications in a Linux environme ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-free-up-and-automatically-manage-disk-space-for-wsl-on-windows-1011/</link>
                <guid isPermaLink="false">6893e671640b08f689368ee6</guid>
                
                    <category>
                        <![CDATA[ WSL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ wsl2 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ disk management ]]>
                    </category>
                
                    <category>
                        <![CDATA[ disk ]]>
                    </category>
                
                    <category>
                        <![CDATA[ disk space ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Powershell ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Windows ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Windows 10 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ windows 11 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ brooklyn ]]>
                </dc:creator>
                <pubDate>Wed, 06 Aug 2025 23:34:09 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754523230294/70893973-fddf-42a9-b41a-2a8f94a47e22.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Windows Subsystem for Linux (<a href="https://learn.microsoft.com/en-us/windows/wsl/install">WSL</a>) lets you run a Linux environment directly on Windows. This is particularly useful for web development where you can develop and test applications in a Linux environment without leaving Windows. You can even run <a href="https://contribute.freecodecamp.org/how-to-setup-wsl/">freeCodeCamp locally</a> with it!</p>
<p>But managing disk space can be a quite a challenge, as WSL uses virtual hard disks that do not automatically free up unused space.</p>
<p>This tutorial will guide you through the process of manually compacting your WSL virtual hard disks. We’ll automate this task using a PowerShell script, ensuring that your WSL environment remains efficient and clutter-free.</p>
<h2 id="heading-reclaim-your-space">Reclaim Your Space</h2>
<p>WSL uses a virtualization platform to install Linux distributions on your Windows system. Each distribution you add gets its own Virtual Hard Disk (VHD), which uses the ext4 file system (common in Linux). It’s saved on your Windows drive as an ext4.vhdx file.</p>
<p>Key issues here:</p>
<ul>
<li><p>Inefficient storage: by default, VHD files <strong>do not reclaim</strong> unused space. This means that when you delete a file in WSL, the associated disk space isn’t immediately freed up.</p>
</li>
<li><p>Disk space consumption: due to that inefficient storage, the <strong>VHD files can grow large</strong> thanks to that accumulated data, especially if you’re a WSL heavy user.</p>
</li>
<li><p>Need for maintenance: you may not know that you need to <strong>compact</strong> your VHD files in order to reclaim disk space.</p>
</li>
</ul>
<p>If you notice that your free disk space is shrinking even after deleting files and apps, WSL might be the reason. This tutorial will help you keep your WSL and Windows environment running smoothly.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-part-1-how-to-manually-compact-your-virtual-hard-disk">Part 1: How to Manually Compact Your Virtual Hard Disk</a></p>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-step-1-verify-your-wsl-version-and-status">Step 1: Verify your WSL version and status</a></p>
</li>
<li><p><a href="#heading-step-2-list-all-installed-distributions-verbosely">Step 2: List all installed distributions verbosely</a></p>
</li>
<li><p><a href="#heading-step-3-locate-your-linux-virtual-hard-drive-vhdx-path">Step 3: Locate your linux Virtual Hard Drive (VHDX) path</a></p>
</li>
<li><p><a href="#heading-step-4-shut-down-all-wsl-instances">Step 4: Shut down all WSL instances</a></p>
</li>
<li><p><a href="#heading-step-5-compact-the-linux-virtual-hard-drive-using-diskpart">Step 5: Compact the Linux virtual hard drive using DiskPart</a></p>
</li>
<li><p><a href="#heading-step-6-restart-wsl-and-verify">Step 6: Restart WSL and verify</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-part-2-how-to-make-your-life-easier-with-automation">Part 2: How to Make Your Life Easier with Automation</a></p>
<ul>
<li><p><a href="#heading-prerequisites-1">Prerequisites</a></p>
</li>
<li><p><a href="#heading-step-1-find-out-installed-wsl2-distributions">Step 1: Find out installed WSL2 distributions</a></p>
</li>
<li><p><a href="#heading-step-2-select-a-distro-to-compact">Step 2: Select a distro to compact</a></p>
</li>
<li><p><a href="#heading-step-3-locate-the-ext4vhdx-file">Step 3: Locate the ext4.vhdx File</a></p>
</li>
<li><p><a href="#heading-step-4-the-confirmation-prompt">Step 4: The confirmation prompt</a></p>
</li>
<li><p><a href="#heading-step-5-shut-down-wsl-and-compact">Step 5: Shut Down WSL and compact</a></p>
</li>
<li><p><a href="#heading-step-6-run-a-diskpart-script">Step 6: Run a DiskPart script</a></p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-part-1-how-to-manually-compact-your-virtual-hard-disk"><strong>Part 1: How to Manually Compact Your Virtual Hard Disk</strong></h2>
<p>Let's start by going through the process manually. This section will guide you through checking your WSL version and associated Linux distributions, finding VHD files, shutting down WSL, and compacting the virtual disk.</p>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<ul>
<li><p>Windows 10 (20H1/2004+) or Windows 11 with WSL2 installed</p>
</li>
<li><p>The PowerShell or Command Prompt running as <strong>Administrator</strong> (from the Windows menu, right click the icon and choose run as Administrator).</p>
</li>
</ul>
<h3 id="heading-step-1-verify-your-wsl-version-and-status"><strong>Step 1: Verify your WSL version and status</strong></h3>
<p>First, make sure you’re running on WSL version 2 (commonly referred as WSL2). The first version is outdated and WSL2 provides significant improvements. Open PowerShell (as Admin) or Command Prompt (as Admin) and run:</p>
<pre><code class="language-powershell">wsl -v

wsl --status
</code></pre>
<p>These commands display the WSL client version and whether your default distro is using WSL 2. Here’s the output of the <code>wsl -v</code> command:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754250376279/ef11af4b-ba5b-43f9-9532-db2634eed154.png" alt="Command prompt displaying WSL version 2.5.9.0, with corrupted or incomplete text following &quot;Kernel version:&quot;, &quot;WSLg version:&quot;, and other version labels." width="421" height="143" loading="lazy">

<p>And here’s the output of the <code>wsl --status</code> command.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754250364365/6cea2d97-0796-4320-8f84-58d1b5e62c5e.png" alt="Command line text showing &quot;C:sers>wsl --status&quot; with the information &quot;Default Distribution: Ubuntu&quot; and &quot;Default Version: 2&quot;." width="243" height="71" loading="lazy">

<h3 id="heading-step-2-list-all-installed-distributions-verbosely"><strong>Step 2: List all installed distributions verbosely</strong></h3>
<p>To see a detailed list of your WSL distributions (including which version each uses), run:</p>
<pre><code class="language-powershell">wsl.exe --list --verbose
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754250281542/1826814a-5516-483e-b8ab-6477fd950e21.png" alt="Output of  the WSL --list --verbose command." width="302" height="67" loading="lazy">

<p>Above you can see the output of the WSL <code>--list --verbose</code> command.</p>
<p>Look for your distro name (for example, “<em>Ubuntu</em>”) and note its WSL version. If it shows “Version 2”, you can proceed with compaction.</p>
<h3 id="heading-step-3-locate-your-linux-virtual-hard-drive-vhdx-path"><strong>Step 3: Locate your linux Virtual Hard Drive (VHDX) path</strong></h3>
<p>Each WSL distro’s files live in a <a href="https://en.wikipedia.org/wiki/VHD_(file_format)">VHDX file</a> on your Windows drive. To find the path for any Linux distribution, use this PowerShell snippet:</p>
<pre><code class="language-powershell">(Get-ChildItem `

-Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss `

| Where-Object { $_.GetValue("DistributionName") -eq 'YOUR_DISTRO_NAME' }

).GetValue("BasePath") + "\ext4.vhdx"
</code></pre>
<p>Where you replace <code>YOUR_DISTRO_NAME</code> with yours (Ubuntu, Debian, Kali-linux..). Here’s the output of the command shown above in PowerShell (the filepath has been anonymized):</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754251303855/ea3b3880-5804-4f50-97c8-327ffd017084.png" alt="A PowerShell command is displayed, used to locate the ext4.vhdx file for the Ubuntu distribution. The command retrieves the Windows Subsystem for Linux (WSL) base path for Ubuntu." width="616" height="74" loading="lazy">

<p>This command reads the registry key for your linux distribution, then appends “\ext4.vhdx” to build the full file path.</p>
<p>Make sure you copy the whole line. We will need it in later stages.</p>
<h3 id="heading-step-4-shut-down-all-wsl-instances"><strong>Step 4: Shut down all WSL instances</strong></h3>
<p>Before you can compact any virtual drive, make sure WSL is completely shut down. In PowerShell or Command Prompt (still as Administrator), run:</p>
<pre><code class="language-powershell">wsl.exe --shutdown
</code></pre>
<h3 id="heading-step-5-compact-the-linux-virtual-hard-drive-using-diskpart"><strong>Step 5: Compact the Linux virtual hard drive using DiskPart</strong></h3>
<p>You successfully gathered all the needed information (about your system, the available distros, and their VHDX filepath) to proceed with the main task. In this step, you actually proceed with the compaction.</p>
<ol>
<li>Launch DiskPart in the same elevated (<em>admin</em>) shell:</li>
</ol>
<pre><code class="language-powershell">diskpart
</code></pre>
<p>DiskPart will open in a new window. It's a Windows command-line tool for managing disk partitions. Be cautious when using it, as incorrect actions can cause serious data loss.</p>
<ol>
<li>In the DiskPart prompt, select the VHDX file you found earlier. Replace the path as displayed below with your actual path (the line you copied before):</li>
</ol>
<pre><code class="language-powershell">select vdisk file="C:\Users\username\AppData\path\to\ext4.vhdx"
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754251748072/91795798-0896-4fcc-994c-0ab311955bee.png" alt="Screenshot of a command prompt window showing Microsoft DiskPart version information. A command is entered to select a virtual disk file, and a message confirms successful selection." width="920" height="150" loading="lazy">

<p>The above is the output of the select vdisk command (some data has been anonymized).</p>
<ol>
<li>Attach the virtual drive in read-only mode:</li>
</ol>
<p>Compaction only needs to scan the empty blocks in the file, not write to the Linux filesystem inside. Read-only mode guarantees that DiskPart only inspect the blocks for zero‐trimming without any chance of damaging or altering your Linux filesystem.</p>
<pre><code class="language-powershell">attach vdisk readonly
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754251937734/a90af720-1511-42cc-bc37-63439d855907.png" alt="Command prompt showing the output for &quot;DISKPART> attach vdisk readonly&quot; with a successful attachment message." width="462" height="107" loading="lazy">

<p>You can see in the screenshot above that the virtual hard drive has been successfully attached.</p>
<ol>
<li>Compact the disk:</li>
</ol>
<p>This frees up the disk space by shrinking the physical size of the <code>.vhdx</code> file to match the <strong>actual used data</strong> inside.</p>
<pre><code class="language-powershell">compact vdisk
</code></pre>
<p>This operation might take a while. When you see the <code>“DiskPart successfully compacted the virtual disk file”</code> message, proceed with the next step. In the image below, the virtual hard drive has been successfully compacted.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754252148605/51cc9739-775b-4a84-a526-7c2a6d2a9722.png" alt="Terminal output showing &quot;DISKPART> compact vdisk&quot; with &quot;100 percent completed,&quot; indicating successful compaction of the virtual disk file." width="450" height="96" loading="lazy">

<ol>
<li>Detach the virtual drive:</li>
</ol>
<pre><code class="language-powershell">detach vdisk
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754252460797/e29387d7-4f03-4ddb-80f7-f14a5051b0fe.png" alt="Command prompt showing &quot;DISKPART> detach vdisk&quot; and a confirmation message that DiskPart successfully detached the virtual disk file." width="446" height="72" loading="lazy">

<p>There you go – the virtual hard drive has been successfully detached.</p>
<p>This command releases any locks on the virtual drive and effectively dismounts it. If you don't use this command, the file remains "in use," preventing WSL (or you) from accessing it until you reboot or manually force it closed.</p>
<p>6. Exit DiskPart:</p>
<pre><code class="language-powershell">exit
</code></pre>
<h3 id="heading-step-6-restart-wsl-and-verify"><strong>Step 6: Restart WSL and verify</strong></h3>
<p>Back in PowerShell or Command Prompt, you can relaunch your distro:</p>
<pre><code class="language-powershell">wsl -d YOUR_DISTRO_NAME
</code></pre>
<p>You can even try the Unix <code>df -h</code> command in your WSL prompt to check your new available disk spaces.</p>
<p>Congrats, you just achieved a maintenance task that can free up lots of gigabytes of storage over time. Now, it’s time to automate.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754321783829/4325a626-806e-47e7-9147-a76f4c91a93a.jpeg" alt="A minimalistic white rectangular button with a cable on a pink surface." style="display:block;margin:0 auto" width="640" height="360" loading="lazy">

<h2 id="heading-part-2-how-to-make-your-life-easier-with-automation"><strong>Part 2: How to Make Your Life Easier with Automation</strong></h2>
<p>Since it's often hard to remember exactly where your WSL distro is located and you probably won't use it very often, this PowerShell script will automate the entire process we covered in part 1. Here's a preview of the steps you'll follow:</p>
<ul>
<li><p>Detect installed WSL distributions.</p>
</li>
<li><p>Select one (and handle the cases there are more than one).</p>
</li>
<li><p>Locate the corresponding <code>ext4.vhdx</code> file.</p>
</li>
<li><p>Shut down WSL and use DiskPart to compact the virtual disk.</p>
</li>
</ul>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<ul>
<li><p>Windows 10 (20H1/2004+) or Windows 11 with WSL 2 enabled.</p>
</li>
<li><p>PowerShell or Command Prompt (as Administrator).</p>
</li>
</ul>
<p>You’ll also need a code editor. The Windows notepad is enough for completing this task. You can also use an IDE (Integrated Development Environment) like VS Code or an ISE (Integrated Scripting Environment) like PowerShell ISE (included with Windows).</p>
<p>To test the script, download it from <a href="https://github.com/hyperphantasia/WSL-VHDX-Compact/blob/c5c2e346ab0dd1a8dbc6130f8d372af8022ddd60/wsl_compactor.ps1">GitHub</a>. Open an elevated PowerShell or Command Prompt and navigate to the script’s folder. With just the command below, you will be able to run it and free up some disk space:</p>
<pre><code class="language-powershell">powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\wsl_compactor.ps1
</code></pre>
<h3 id="heading-step-1-find-out-installed-wsl2-distributions"><strong>Step 1: Find out installed WSL2 distributions</strong></h3>
<p>One of the main challenges is to find the Linux distributions available on the host system. Let’s check the first block and see what’s it about:</p>
<pre><code class="language-powershell">$lxssKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss'
\(distros = Get-ChildItem \)lxssKey | ForEach-Object {
    \(p = Get-ItemProperty \)_.PSPath
    [PSCustomObject]@{
        Name     = $p.DistributionName
        BasePath = $p.BasePath
    }
}
</code></pre>
<p>WSL records each distribution under this windows registry key:</p>
<p><code>HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss</code></p>
<p>Each subkey has two important values:</p>
<ul>
<li><p><strong>DistributionName</strong> (for example, Ubuntu)</p>
</li>
<li><p><strong>BasePath</strong> (This is where the distribution files are stored. It’s the directory that contains the <code>ext4.vhdx</code> file.)</p>
</li>
</ul>
<p>The script uses <code>Get-ChildItem</code> and <code>Get-ItemProperty</code> to enumerate these subkeys and build a list of available Linux distributions.</p>
<pre><code class="language-powershell">if ($distros.Count -eq 0) {
Throw-And-Exit "No WSL distros found in the registry."
}
</code></pre>
<p>If no distributions are found, the script terminates and prints this error message on the terminal: <code>"No WSL distros found in the registry.”</code></p>
<h3 id="heading-step-2-select-a-distro-to-compact"><strong>Step 2: Select a distro to compact</strong></h3>
<p>Here, the process has two steps:</p>
<ul>
<li>If multiple distros are found, it displays all the distros with a numbered menu and prompts you to choose one:</li>
</ul>
<pre><code class="language-powershell">if ($distros.Count -gt 1) {
    Write-Host "Multiple distros detected. Please choose one:`n"

    for (\(i = 0; \)i -lt \(distros.Count; \)i++) {
        Write-Host "[\((\)i+1)] \((\)distros[$i].Name)"
    }
    \(selected = \)distros[[int]$choice - 1]
}
</code></pre>
<p>The computed menu will look like this:</p>
<pre><code class="language-markdown">Multiple distros detected. Please choose one:

[1] Ubuntu 20.04

[2] Debian

[3] Alpine
</code></pre>
<ul>
<li>If only one distribution is found on the host system, the script selects it automatically:</li>
</ul>
<pre><code class="language-powershell">else {
\(selected = \)distros[0]
}
</code></pre>
<p>When setting up a distribution, whether chosen manually by the user or selected automatically, <strong>the important information is the path to each distribution's virtual hard disk</strong>. This path is saved in two main variables: <code>'distro'</code> (which identifies the specific distribution) and <code>'basePath'</code> (which shows where its virtual disk is located).</p>
<pre><code class="language-powershell">\(distro = \)selected.Name
\(basePath = \)selected.BasePath

Write-Host "`nSelected distro: $distro" -ForegroundColor DarkYellow
Write-Host "BasePath: $basePath"
</code></pre>
<p>The lines above display an output that looks like this:</p>
<pre><code class="language-markdown">Selected distro: Ubuntu (or any other distro)
BasePath: C:\Users\&lt;User_name&gt;\AppData\Local\Packages\…
</code></pre>
<p>Like for all other steps, it’s important to consider the case of something going wrong, by throwing an error and exiting the program:</p>
<pre><code class="language-powershell">if (-not (Test-Path $basePath)) {
Throw-And-Exit "BasePath '$basePath' does not exist on disk."
}
</code></pre>
<h3 id="heading-step-3-locate-the-ext4vhdx-file"><strong>Step 3: Locate the ext4.vhdx File</strong></h3>
<p>In the first step, we collected the information we need about the available distributions and where they are stored on the Windows system. By choosing an entry (either manually or automatically), we can find the correct file. Sometimes, the ext4 file is located between the base path and a <em>LocalState</em> folder. This script manages both situations. It builds the usual locations where the file can be found.</p>
<p>They look like this:</p>
<ul>
<li><p><code>$BasePath\ext4.vhdx</code></p>
</li>
<li><p><code>$BasePath\LocalState\ext4.vhdx</code></p>
</li>
</ul>
<p>This can translate into something like this on your system (option 1):</p>
<pre><code class="language-markdown">C:\Users\Alice\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu20.04onWindows_79rhkp1fndgsc\ext4.vhdx
</code></pre>
<p>or like this (option 2):</p>
<pre><code class="language-markdown">C:\Users\Alice\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu20.04onWindows_79rhkp1fndgsc\LocalState\ext4.vhdx
</code></pre>
<p>(You might find out that your WSL2 distro is located in some other directory than “Packages” – but don’t worry, your BasePath will match the correct folders).</p>
<p>The idea is to build the two possible path options:</p>
<pre><code class="language-powershell">$possible = @(
Join-Path $basePath 'ext4.vhdx'
Join-Path $basePath 'LocalState\ext4.vhdx'
)
</code></pre>
<p>And pick the first one that actually contains the file:</p>
<pre><code class="language-powershell">\(vhdx = \)possible | Where-Object { Test-Path $_ } | Select-Object -First 1
</code></pre>
<p>Again, we throw an error message if no suitable file is found:</p>
<pre><code class="language-powershell">if (-not $vhdx) {
Throw-And-Exit "No ext4.vhdx found under '$basePath'."
}
</code></pre>
<h3 id="heading-step-4-the-confirmation-prompt"><strong>Step 4: The confirmation prompt</strong></h3>
<p>Disk management tools require caution and you need to understand the potential consequences of your actions. A confirmation prompt is always a good safeguard to prevent accidental data loss or unwanted system changes.</p>
<p>Before proceeding, the script shows you:</p>
<ul>
<li><p>a Distro name</p>
</li>
<li><p>its BasePath</p>
</li>
<li><p>the VHDX file path</p>
</li>
</ul>
<pre><code class="language-powershell">Write-Host "`nAbout to compact this WSL distro:" -ForegroundColor Magenta
Write-Host " Distro : $distro"
Write-Host " BasePath : $basePath"
Write-Host " VHDX file: $vhdx`n"
</code></pre>
<p>It then prompts <strong>“Are you sure you want to proceed? (Y/N)”:</strong></p>
<pre><code class="language-powershell">Write-Host "Are you sure you want to proceed? (Y/N) " -ForegroundColor DarkCyan -NoNewline

# Then read the response
$answer = Read-Host
</code></pre>
<p>You’re then prompted to Type Y (case-insensitive) to continue or anything else to cancel.</p>
<pre><code class="language-powershell">if ($answer.ToUpper() -ne 'Y') {
    Write-Warning "Operation canceled"
    exit
}
</code></pre>
<p>For the two steps above, I had to use a trick to print the question in color but a simple option (without colors) could be:</p>
<pre><code class="language-powershell">if ((Read-Host 'Are you sure you want to proceed? (Y/N)').ToUpper() -ne 'Y') { 
    Write-Warning 'Operation canceled'
    exit 
}
</code></pre>
<h3 id="heading-step-5-shut-down-wsl-and-compact"><strong>Step 5: Shut down WSL and compact</strong></h3>
<p>Before proceeding to the DiskPart utility, it’s important to stop all running WSL instances. Pass the shutdown command directly in PowerShell.</p>
<pre><code class="language-powershell">Write-Host "Shutting down WSL…" -ForegroundColor Cyan
wsl.exe –shutdown
</code></pre>
<p>A common mistake is to forget to launch PowerShell or the Command Prompt with Administrator rights. You can prevent this case with a message:</p>
<pre><code class="language-powershell">if ($LASTEXITCODE -ne 0) {
     Throw-And-Exit "Failed to shut down WSL (exit code $LASTEXITCODE). Are you running as Administrator?"
}
</code></pre>
<h3 id="heading-step-6-run-a-diskpart-script"><strong>Step 6: Run a DiskPart script</strong></h3>
<h4 id="heading-building-the-script">Building the script:</h4>
<p>The process is the same as in the manual part, but this time, we ‘inject’ the ready-to-go DiskPart commands into the script.</p>
<pre><code class="language-powershell">$dpScript = @"
select vdisk file="$vhdx"
attach vdisk readonly
compact vdisk
detach vdisk
exit
"@
</code></pre>
<p>Before launching, there are two steps you need to take:</p>
<ol>
<li>The PowerShell script writes the lines above to a temporary file:</li>
</ol>
<pre><code class="language-powershell">$tempFile = [IO.Path]::GetTempFileName()
Set-Content -LiteralPath \(tempFile -Value \)dpScript -Encoding ASCII
</code></pre>
<p>This is the equivalent to the commands passed in the manual part:</p>
<p><code>select vdisk file="</code><a href="/home/C:/"><code>C:\</code></a><code>…\ext4.vhdx" # full path to the vdisk file</code></p>
<p><code>attach vdisk readonly</code></p>
<p><code>compact vdisk</code></p>
<p><code>detach vdisk</code></p>
<p><code>exit</code></p>
<ol>
<li>Compacting can take a while, especially if you’ve never de-cluttered your virtual drive before. It’s wise to show a warning before proceeding:</li>
</ol>
<pre><code class="language-powershell">Write-Host "Running DiskPart to compact the VHDX. Be patient, this might take a while..." -ForegroundColor Cyan
</code></pre>
<h4 id="heading-invoke-diskpart"><strong>Invoke DiskPart:</strong></h4>
<pre><code class="language-powershell"># Run DiskPart with the script saved to the temporary file and process each output line as it arrives
diskpart /s $tempFile | ForEach-Object {
    # Grab any "NN percent" type message from the line
    if ($_ -match '(\d+)\s+percent') {
        # Only print when the percentage actually changes
        Write-Host "\((\)Matches[1])% completed"
    }
    else {
        # Just echo all over line-types, verbatim
        Write-Host $_
    }
}
</code></pre>
<p>Several points to note here:</p>
<ul>
<li><p>It runs <code>diskpart /s $tempFile</code>: DiskPart reads and executes commands from the temporary file into the PowerShell loop for on-the-fly processing.</p>
</li>
<li><p>For a better user-experience: the snippet below does the trick of filtering out repeated status values by comparing <code>\(pct</code> with the sentinel <code>\)lastPct</code>, and only writing new lines when they differ.</p>
</li>
</ul>
<p>How?</p>
<p>Before entering the loop, we initialize:</p>
<pre><code class="language-powershell">$lastPct with -1
$lastPct = -1 # We initiate a sentinel value
</code></pre>
<p>We are having a guaranteed “first” value that no real percent (0–100) will equal. That way, as soon as you see the first 0 percent, 10 percent, or whatever, it differs from -1.</p>
<p>Then:</p>
<pre><code class="language-powershell">if ($_ -match '(\d+)\s+percent') {
    # Print only when the percentage changes
    Write-Host "\((\)Matches[1])% completed"
}
</code></pre>
<p>This guarantees that on the very first percentage update (say “0 percent” or “10 percent”), <code>\(pct –ne \)lastPct</code> will be <code>true</code>, so it emits the first line. Afterwards, <code>$lastPct</code> holds the last real percentage, and it only prints again when a new, different progress percentage comes in.</p>
<p>The output looks more clean:</p>
<pre><code class="language-markdown">10% completed
20% completed
…
</code></pre>
<p>Otherwise, it’ll flood the screen with dozens of identical “20 percent completed” (for example) updates.</p>
<p>Of course we handle the case of other values (non percent lines) normally:</p>
<pre><code class="language-powershell">else {
    # non-percent lines: print verbatim
    if ($_ -match '\S') {
        Write-Host $_
    }
}
</code></pre>
<p>Once the process is done, don’t forget to clean up the tempfile.</p>
<pre><code class="language-powershell">Remove-Item $tempFile -ErrorAction SilentlyContinue
</code></pre>
<p>By the end of the process, you should see something like this</p>
<pre><code class="language-markdown">Leaving DiskPart...
</code></pre>
<p>Okay, that’s it for the scripting! If you collected all the snippets so far, just save them with a <code>.ps1</code> file extension, or download the full example from this <a href="https://github.com/hyperphantasia/WSL-VHDX-Compact">GitHub repository</a>.</p>
<h3 id="heading-usage"><strong>Usage:</strong></h3>
<p>You now have a complete understanding of what's happening. Ready to get started? In Windows, open the Command Prompt or PowerShell <strong>as an administrator.</strong></p>
<ul>
<li><p>Navigate to the directory containing your <code>.ps1</code>&nbsp;script.</p>
</li>
<li><p>Execute the script with the following command, replacing <code>&lt;File_name_here&gt;</code> with your actual file name:</p>
</li>
</ul>
<pre><code class="language-powershell">powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\&lt;File_name_here&gt;.ps1
</code></pre>
<p>The <code>-NoProfile -ExecutionPolicy Bypass</code> parameters launch PowerShell in a clean, unrestricted environment that ignores user-specific settings and allows script execution without security restrictions. Don’t worry, in this case, it’s okay to do that.</p>
<p><em>Wait…wait…wait...</em></p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754315563256/a709192a-6626-4a12-b411-46c8591c5eb6.jpeg" alt="Screenshot of a command line interface showing the execution of a PowerShell script to compact a WSL (Windows Subsystem for Linux) distro. The script confirms the selected distro &quot;Ubuntu&quot; and proceeds to compact the VHDX file using DiskPart. Progress is shown in percentage increments, with final messages indicating successful completion of the compacting process." style="display:block;margin:0 auto" width="878" height="500" loading="lazy">

<p>Well bravo! You’ve just reclaimed all that unused space inside your WSL2 (almost) hands free!</p>
<p>Now, you can take it a step further by modifying this script to run completely automatically, without the confirmation prompt (step 4). You can also schedule it as a regular maintenance task using a task scheduler:</p>
<pre><code class="language-powershell">schtasks /create /tn "Schedule_name" /tr "powershell.exe -ExecutionPolicy Bypass -File C:\path\to\script.ps1" /sc monthly /d 15 /st 09:00
</code></pre>
<p>This is an example for a monthly execution where <code>/d 15</code> means 15th of each month and <code>/st 09:00</code> is a start time set at 9 am.</p>
<p>That's it! Remember, regular maintenance, whether you do it manually or automatically, is essential to prevent unnecessary disk space usage and ensure a smooth experience with WSL.</p>
<h3 id="heading-thanks-for-reading">Thanks for reading!</h3>
<p>You can find a list of my current projects on <a href="https://github.com/hyperphantasia?tab=repositories">GitHub</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Protect Your GitHub Repos Against Malicious Clones ]]>
                </title>
                <description>
                    <![CDATA[ The world of open-source development comes with various cyber threats. GitHub is still facing a type of attack that is ongoing since last year where attackers mirrored a huge number of repositories. S ]]>
                </description>
                <link>https://www.freecodecamp.org/news/protect-github-repos-from-malicious-clones/</link>
                <guid isPermaLink="false">68781639d48174ae283f8a5b</guid>
                
                    <category>
                        <![CDATA[ repo confusion ]]>
                    </category>
                
                    <category>
                        <![CDATA[ repository confusion ]]>
                    </category>
                
                    <category>
                        <![CDATA[ fake repos ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ clone ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #cybersecurity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Malware ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Supply Chain Attack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ malicious ]]>
                    </category>
                
                    <category>
                        <![CDATA[ checklist ]]>
                    </category>
                
                    <category>
                        <![CDATA[ phishing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ infostealer ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Prevention ]]>
                    </category>
                
                    <category>
                        <![CDATA[ best practices ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ brooklyn ]]>
                </dc:creator>
                <pubDate>Wed, 16 Jul 2025 21:14:33 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752700407765/5fe06816-3d3a-40e4-8a4e-5cfe96a22368.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The world of open-source development comes with various cyber threats. GitHub is still facing a type of attack that is ongoing since last year where attackers mirrored a huge number of repositories. So as it turns out…the clone wars are not over!</p>
<p>If you haven’t heard about what’s going on:</p>
<blockquote>
<p>GitHub is struggling to contain an ongoing attack that’s flooding the site with with millions of code repositories. These repositories contain obfuscated malware that steals passwords and cryptocurrency from developer devices. … The result is millions of forks with names identical to the original one.</p>
<p>–&nbsp;Dan Goodin, <a href="https://arstechnica.com/security/2024/02/github-besieged-by-millions-of-malicious-repositories-in-ongoing-attack/">Ars technica</a></p>
</blockquote>
<p>Because search engines and GitHub’s own search rankings favor recent activity, these cloned repositories often float to the top – then they lure unsuspecting developers into pulling code that may contain malware.</p>
<p>One of my <a href="http://github.com/hyperphantasia/miniature-fortnight">repositories</a> has been targeted by such an attack, prompting me to monitor it closely. This guide offers tips to spot malicious repository clones before they catch you off guard.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ol>
<li><p><a href="#heading-what-is-a-repository-confusion-attack">What is a Repository Confusion Attack?</a></p>
<ul>
<li><a href="#heading-supply-chain-attacks">Supply Chain Attacks</a></li>
</ul>
</li>
<li><p><a href="#heading-basic-mitigation-strategies">🛡️ Basic Mitigation Strategies</a></p>
<ul>
<li><p><a href="#heading-verify-the-contributors-profiles">Verify the contributors profiles</a></p>
</li>
<li><p><a href="#heading-search-for-clone-repositories">Search for clone repositories</a></p>
</li>
<li><p><a href="#heading-examine-the-commit-pattern">Examine the commit pattern</a></p>
</li>
<li><p><a href="#heading-examine-the-commit-history">Examine the commit history</a></p>
</li>
<li><p><a href="#heading-examine-the-commit-contents">Examine the commit contents</a></p>
</li>
<li><p><a href="#heading-compare-the-concerned-files">Compare the concerned files</a></p>
</li>
<li><p><a href="#heading-some-information-about-the-malware">Some information about the malware</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-action-time">Action Time</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-more-resources">More Resources</a></p>
</li>
</ol>
<h2 id="heading-what-is-a-repository-confusion-attack"><strong>What is a Repository Confusion Attack?</strong></h2>
<p>A repository confusion attack involves:</p>
<ul>
<li><p>Cloning legitimate repositories.</p>
</li>
<li><p>Injecting malicious code into the clone.</p>
</li>
<li><p>Uploading the clone.</p>
</li>
<li><p>Spreading through various unaware actors.</p>
</li>
</ul>
<h3 id="heading-supply-chain-attacks"><strong>Supply Chain Attacks</strong></h3>
<p>If you search for repository confusion on the internet, you'll find out it's a type of <em>supply chain attack</em>.</p>
<p>A supply chain attack is an&nbsp;<em>indirect</em>&nbsp;threat where hackers try infiltrating a system by targeting a trusted third-party or software component, rather than attacking the primary target directly.</p>
<p>It's not the first time this has happened. Before GitHub was targeted, PyPI was attacked in 2023 with&nbsp;<a href="https://arstechnica.com/information-technology/2023/01/more-malicious-packages-posted-to-online-repository-this-time-its-pypi/">fake packages</a>&nbsp;posing as legitimate. These packages lured negligent pip users into downloading malicious payloads (containing in most cases&nbsp;<a href="https://en.wikipedia.org/wiki/Infostealer">infostealer malware</a>).</p>
<h2 id="heading-basic-mitigation-strategies"><strong>🛡️ Basic Mitigation Strategies</strong></h2>
<p><strong>Before</strong>&nbsp;using any repository, make sure you follow these steps and take these precautions.</p>
<h3 id="heading-verify-the-contributors-profiles"><strong>Verify the contributors profiles</strong></h3>
<p>That's a first check: if you see a rather empty GitHub profile&nbsp;– one without reputation that contains just one repository but with a lot of daily commits to it – well, that's a bit suspicious.</p>
<p>In the fake repository, the original author will be listed as a contributor, too. Check that profile. You should be able to find the legitimate repository and do some comparisons.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752335573817/c39aca11-2605-47a2-8a6b-aded16547783.png" alt="GitHub screenshot of a repository contributors" width="316" height="156" loading="lazy">

<p>In the above screenshot you can see solotech143, my evil doppelgänger <em>(he’s been taken down since)</em>.</p>
<h3 id="heading-search-for-clone-repositories"><strong>Search for clone repositories</strong></h3>
<p>You can do a GitHub search by repository name and sort the results by most recent first. Malicious repositories tend to appear at the top of the search results because they are updated more frequently. The original repository might be hidden deeper in the search results.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752335785307/943c6dd3-aa28-4d72-b63a-65736de06dcf.png" alt="GitHub clone search results." width="1172" height="314" loading="lazy">

<p>It’s like clone wars.</p>
<p><strong>This is where it’s dangerous:</strong> users generally click on the first few search results, and in that type of attack, you’re almost guaranteed to see the attacker’s fake repository at the top of the results. The attacker achieves that by giving the fake repository regular fresh commits (and sometimes even a few stars!).</p>
<p>In my case, the original repository is a submission for the HackaViz 2025 competition. Hackathons offer a good attack surface because, beyond the fact they draw niche communities, they are also time sensitive.</p>
<p>Now, let’s move forward a year and imagine Hackaviz 2026 is starting soon. The attacker has easily outranked the untouched original submission. Which repository is most likely to be visited when future competitors – unaware of the scam – will look for the previous submissions?</p>
<h3 id="heading-examine-the-commit-pattern"><strong>Examine the commit pattern</strong></h3>
<p>Here’s when things take a weird turn. Malicious clones are run by automated agents, so the commit history fits a pattern that is rather unusual for a human. Of course, you can automate for many legitimate reasons but… this will always follow a clear goal and there will always be a human-touch at some point. In this case, commits are not adding up.</p>
<p>Let's see how that looks in the screenshots below:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752335872381/1238dee9-3568-4d2b-88bb-f63258ffb045.png" alt="1238dee9-3568-4d2b-88bb-f63258ffb045" width="390" height="197" loading="lazy">

<p>Regular like a clock...</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752335891381/77f835fe-cccf-409f-85d7-789f918d4aa3.png" alt="A GitHub screenshot of a very active contribution activity.." width="764" height="602" loading="lazy">

<p>... and hyperactive!</p>
<h3 id="heading-examine-the-commit-history"><strong>Examine the commit history</strong></h3>
<p>You can’t! And that's the weird part. You're just able to see the last and the initial commit. So why is it hiding all of them? Do you like it when someone hide things from you?</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752336385127/6274dd87-0a97-4c38-8849-9d547b9edb22.png" alt="A github commits history screenshot for one day." width="1304" height="225" loading="lazy">

<p>For July 10th, we should be able to see 11 commits, where are the ten others?</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752336355084/4c7c343d-2000-4359-ae98-dcc98fb08732.png" alt="A github commits history screenshot for a whole period." width="1307" height="369" loading="lazy">

<p>Well, you can only check the first and last commit. That is not a lot for a repository that has more than 2000 commits registered.</p>
<h3 id="heading-examine-the-commit-contents"><strong>Examine the commit contents</strong></h3>
<p>Well, since I can always check the last commit, I checked some of them. They share the same pattern: the bot is constantly looping over the README file doing the same modifications. As you can see in the screenshot below, it’s updating the file with links to an infected release.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752336493881/e8b57b4c-4d13-44f8-bca6-bd8fbad2738c.png" alt="A github screenshot of commits to a malicious repository." width="1382" height="800" loading="lazy">

<p>Above you can see an AI agent stuck in the Readme loop of change.</p>
<p>Human edits are more varied. In a human-driven project, you will see a large mix of commits: feature commits, exploratory experiments, bug fixes, styling tweaks, and sometimes reverts. A bot clone will often just overwrite files, bump versions, or re-inject the same malicious payload repeatedly with no real contribution to the codebase.</p>
<h3 id="heading-compare-the-concerned-files"><strong>Compare the concerned files</strong></h3>
<p>This is where common sense comes handy. So, you have two README's:</p>
<ol>
<li><p>The&nbsp;<a href="https://web.archive.org/web/20250711182419/https://github.com/solotech143/miniature-fortnight/blob/main/README.md">first</a>&nbsp;consists of AI-generated content that is cluttered with emojis and low-value information. It is designed solely to entice you into clicking the download link of the release.</p>
</li>
<li><p>The <a href="https://github.com/hyperphantasia/miniature-fortnight/blob/main/README.md">other</a> follows <a href="https://www.freecodecamp.org/news/how-to-write-a-good-readme-file/">best practices</a> for creating a good README file. It is accurate and well-structured and functions as a valuable helper and explainer to the code. It also goes deep into the most important aspects of the project. This is usually a good sign that a repository is organic and genuine.</p>
</li>
</ol>
<h3 id="heading-some-information-about-the-malware"><strong>Some information about the malware</strong></h3>
<p>What do we have so far? Well, a suspicious link in a phishy, AI-generated README file that is consistent with a very suspicious pattern in the commit history.</p>
<p>Now, let’s have a closer look at that dubious release and let’s see what an online antivirus scanner might reveal about it.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752336589656/124aecf2-39e9-4158-9a06-f0fd59cbf8c1.png" alt="A  github screenshot of commits to a malicious repository." width="1216" height="404" loading="lazy">

<p>The malware is packed only in the miniature-fortnight-v1.7.6.zip release.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752336609780/7eaf7fc8-73f2-4d9d-b169-1d5c50ce84f2.png" alt="A malware analysis result." width="1317" height="757" loading="lazy">

<p>Above you can see the result of a scan with an online scanner.</p>
<p>The .zip file contains <strong>only</strong> four files:</p>
<ul>
<li><p>config.txt</p>
</li>
<li><p>launch.bat</p>
</li>
<li><p>lua51.dll</p>
</li>
<li><p>luajit.exe</p>
</li>
</ul>
<p>These files are <strong>totally unrelated</strong> to the source project (a Python data science project with Jupyter notebooks combined to a React app using three.js).</p>
<p>I will not go into the detail in this article. But for the curious ones, it's an infostealer malware (a malware that will exfiltrate your credentials and other precious information about your configuration) similar to the one described in&nbsp;<a href="https://www.trendmicro.com/en_us/research/25/c/ai-assisted-fake-github-repositories.html#">detail here</a>.</p>
<h2 id="heading-action-time"><strong>Action Time</strong></h2>
<p>If you discover a potentially malicious repository, here are some steps you can take:</p>
<ol>
<li><p>Document some evidence.</p>
</li>
<li><p>Notify the original repository maintainers.</p>
</li>
<li><p>Report the malicious clone to GitHub.</p>
</li>
</ol>
<p>Reporting a repository or a profile on GitHub is easy and fast. Go to the user’s profile page, click “Block or report” in the left sidebar and choose “Report abuse” in the pop-up. You will have to complete a short contact form with some details about the behavior before submitting. If needed, you can find more information on <a href="https://docs.github.com/en/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam#reporting-a-user">GitHub</a>.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>This is a description of just one attack, from the perspective of someone who found out that one of his repository had been targeted. There are likely cases of more sophisticated attacks. But the clone repository flood we can see on GitHuB is definitely massive low quality automation. Quantity over quality.<br>To be honest, I'm quite surprised algorithms crafted at GitHub didn't manage to spot this one.</p>
<p>This also raises questions related to AI.</p>
<ul>
<li><p>What happens when LLMs are trained on malicious content? That’s a more general question about&nbsp;<a href="https://arstechnica.com/information-technology/2024/01/ai-poisoning-could-turn-open-models-into-destructive-sleeper-agents-says-anthropic/">AI poisoning</a>.</p>
</li>
<li><p>A human might easily spot the patterns and the low quality content&nbsp;<em>for now</em>. But..</p>
<ul>
<li><p>Imagine you are using coding agents, many of them. Will the agents pick-up the malicious clone instead of the original one? How to distinguish the repositories from an automaton's perspective?</p>
</li>
<li><p>The attackers <strong>will</strong> refine their tactics, making the clones more human-like and therefore luring us more easily into their traps.</p>
</li>
</ul>
</li>
<li><p>This is really a situation that makes me wonder about the early days of Google. Back then, the company had to fight huge amounts of spam due to keyword stuffing and manipulative SEO tactics. Will big tech companies have to go through a&nbsp;<a href="https://en.wikipedia.org/wiki/Timeline_of_Google_Search#Full_timeline">Florida update</a>&nbsp;moment to face the rise of AI generated spam ?</p>
</li>
</ul>
<h2 id="heading-more-resources"><strong>More Resources</strong></h2>
<ul>
<li><p><a href="https://www.trendmicro.com/en_be/research/23/j/infection-techniques-across-supply-chains-and-codebases.html">A detailed description of the attack</a></p>
</li>
<li><p><a href="https://www.cisa.gov/sites/default/files/publications/ESF_SECURING_THE_SOFTWARE_SUPPLY_CHAIN_DEVELOPERS.PDF">Complete safety recommendations</a></p>
</li>
</ul>
<p><strong>Stay Informed, Stay Secure!</strong></p>
<p>A <strong>cheat-sheet</strong> is also available on my&nbsp;<a href="https://github.com/hyperphantasia/repo-confusion-guard">GitHub</a>. Feel free to contribute to it!</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
