<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>abdus.dev</title>
        <link>https://abdus.dev</link>
        <description>personal website of Abdussamet Kocak</description>
        <generator>kopkop</generator>
        <atom:link href="https://abdus.dev/rss.xml" rel="self" type="application/rss+xml"/>
        <lastBuildDate>Wed, 21 Oct 2026 00:00:00 +0000</lastBuildDate>
        <item>
            <title>demotime</title>
            <pubDate>Wed, 21 Oct 2026 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/demotime/</link>
            <guid>https://abdus.dev/projects/demotime/</guid>
            <description>&lt;p&gt;I&#x27;ve built this app for myself to trim down demo videos by cutting off sections where nothing happens, e.g. when waiting for a command to finish.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/projects/demotime/./demotime.png&#34; alt=&#34;demotime web app screenshot&#34; /&gt;&lt;&#x2F;p&gt;&#xA;&lt;p&gt;I could use a video editor, but I don&#x27;t want to spend time picking out the sections manually. Turns out ffmpeg has a filter for this. So I first built a Python script, then I quickly realized that this could be better off as a web app.&lt;&#x2F;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;a class=&#34;repo-card&#34; href=&#34;https://github.com/abdusco/demotime&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&#xA;    &lt;span class=&#34;repo-card__name&#34;&gt;&#xA;        &lt;span class=&#34;repo-card__owner&#34;&gt;abdusco&lt;&#x2F;span&gt;&#xA;        &lt;span aria-hidden=&#34;true&#34;&gt;/&lt;&#x2F;span&gt;&#xA;        &lt;strong&gt;demotime&lt;&#x2F;strong&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__description&#34;&gt;A minimal web app for trimming still sections off of a video&lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__meta&#34;&gt;&#xA;        &#xA;        &lt;span&gt;JavaScript&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span aria-label=&#34;0 stars&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-star&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;        &lt;span aria-label=&#34;0 forks&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-fork&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;&lt;&#x2F;a&gt;&#xA;&#xA;&lt;p&gt;The app is deployed as a PWA, so you can install it on your device. It&#x27;s available at &lt;a href=&#34;https://demotime.pages.dev&#34;&gt;https://demotime.pages.dev&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>subbed</title>
            <pubDate>Tue, 20 Oct 2026 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/subbed/</link>
            <guid>https://abdus.dev/projects/subbed/</guid>
            <description>&lt;p&gt;I&#x27;ve created this app to share videos with my friends who don&#x27;t speak English. It lets you import a Youtube video, transcribe its audio into an SRT subtitle file, then translate it into different languages.&lt;&#x2F;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;a class=&#34;repo-card&#34; href=&#34;https://github.com/abdusco/subbed&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&#xA;    &lt;span class=&#34;repo-card__name&#34;&gt;&#xA;        &lt;span class=&#34;repo-card__owner&#34;&gt;abdusco&lt;&#x2F;span&gt;&#xA;        &lt;span aria-hidden=&#34;true&#34;&gt;/&lt;&#x2F;span&gt;&#xA;        &lt;strong&gt;subbed&lt;&#x2F;strong&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__meta&#34;&gt;&#xA;        &#xA;        &lt;span&gt;Go&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span aria-label=&#34;0 stars&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-star&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;        &lt;span aria-label=&#34;0 forks&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-fork&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;&lt;&#x2F;a&gt;&#xA;&#xA;&lt;p&gt;It has a built-in player that overlays the subtitles on top of the video, so you can watch it as if the original creator had provided subtitles in your language.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;It uses &lt;a href=&#34;https://github.com/yt-dlp/yt-dlp&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;yt-dlp&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; under the hood. So you have to copy over your Youtube cookies to download the audio. You can also upgrade it to the latest nightly build when the existing version stops working.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;The app also lets you use custom models from OpenRouter to transcribe the audio and translate the subtitles.&lt;&#x2F;p&gt;&#xA;&lt;image-gallery class=&#34;image-gallery&#34; role=&#34;region&#34; aria-label=&#34;Media gallery&#34;&gt;&#xA;    &lt;div class=&#34;image-gallery__track&#34; tabindex=&#34;0&#34; aria-label=&#34;Scroll to see more images and videos&#34;&gt;&#xA;        &lt;img src=&#34;./subbed_login.png&#34; alt=&#34;login screen for the app&#34; /&gt;&#xA;&lt;img src=&#34;./subbed_library.png&#34; alt=&#34;library view showing available videos and subtitles&#34; /&gt;&#xA;&lt;img src=&#34;./subbed_menu.png&#34; alt=&#34;settings menu&#34; /&gt;&#xA;&lt;img src=&#34;./subbed_settings.png&#34; alt=&#34;settings screen&#34; /&gt;&#xA;&lt;img src=&#34;./subbed_add.png&#34; alt=&#34;popup for adding new videos&#34; /&gt;&#xA;&lt;video src=&#34;./subbed_transcript.mp4&#34; controls playsinline preload=&#34;metadata&#34;&gt;&lt;&#x2F;video&gt;&#xA;&lt;video src=&#34;./subbed_translate.mp4&#34; controls playsinline preload=&#34;metadata&#34;&gt;&lt;&#x2F;video&gt;&#xA;    &lt;&#x2F;div&gt;&#xA;&lt;&#x2F;image-gallery&gt;&#xA;&#xA;</description>
        </item>
        <item>
            <title>wp</title>
            <pubDate>Tue, 13 Oct 2026 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/wp/</link>
            <guid>https://abdus.dev/projects/wp/</guid>
            <description>&lt;div class=&#34;app-logo&#34;&gt;&lt;img src=&#34;wp-icon.svg&#34; alt=&#34;wp app icon&#34;/&gt;&lt;&#x2F;div&gt;&#xA;&lt;style&gt;&#xA;.app-logo {&#xA;    width: 8rem;&#xA;    height: 8rem;&#xA;    margin-bottom: 2rem;&#xA;}&#xA;&lt;&#x2F;style&gt;&#xA;&lt;p&gt;wp is a menu app that offers a minimal wallpaper switcher. It lets you cycle through your favorite wallpapers on a timeout or with a hotkey. It applies a blur effect and lets you reveal the image with double click on the desktop.&lt;&#x2F;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;a class=&#34;repo-card&#34; href=&#34;https://github.com/abdusco/wp&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&#xA;    &lt;span class=&#34;repo-card__name&#34;&gt;&#xA;        &lt;span class=&#34;repo-card__owner&#34;&gt;abdusco&lt;&#x2F;span&gt;&#xA;        &lt;span aria-hidden=&#34;true&#34;&gt;/&lt;&#x2F;span&gt;&#xA;        &lt;strong&gt;wp&lt;&#x2F;strong&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__description&#34;&gt;A minimal macOS wallpaper switcher&lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__meta&#34;&gt;&#xA;        &#xA;        &lt;span&gt;Swift&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span aria-label=&#34;0 stars&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-star&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;        &lt;span aria-label=&#34;0 forks&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-fork&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;&lt;&#x2F;a&gt;&#xA;&#xA;&lt;p&gt;It can also stack multiple portrait images side-by-side to create a collage wallpaper. It has a settings panel to configure the hotkeys, auto-cycle timeout, HTTP API port, and other options.&lt;&#x2F;p&gt;&#xA;&lt;image-gallery class=&#34;image-gallery&#34; role=&#34;region&#34; aria-label=&#34;wp screenshots&#34;&gt;&#xA;    &lt;div class=&#34;image-gallery__track&#34; tabindex=&#34;0&#34; aria-label=&#34;Scroll to see more images and videos&#34;&gt;&#xA;        &lt;img src=&#34;./wp-menu.png&#34; alt=&#34;wp menu app screenshot&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;img src=&#34;./wp-settings.png&#34; alt=&#34;wp settings panel screenshot&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;img src=&#34;./wp-sources.png&#34; alt=&#34;wp settings panel screenshot&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;img src=&#34;./wp-hotkeys.png&#34; alt=&#34;wp settings panel screenshot&#34; loading=&#34;lazy&#34;&gt;&#xA;    &lt;&#x2F;div&gt;&#xA;&lt;&#x2F;image-gallery&gt;&#xA;&#xA;&lt;p&gt;The HTTP server is meant for remote control, which you can activate by specifying a &lt;code&gt;PORT&lt;&#x2F;code&gt; environment variable or in the settings panel. You can use &lt;code&gt;curl&lt;&#x2F;code&gt; to send commands to it e.g. with Alfred on a hotkey press.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;bash&#34; data-highlighted=&#34;true&#34; class=&#34;language-bash z-code z-chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;z-nv&#34;&gt;PORT&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-m&#34;&gt;9876&lt;&#x2F;span&gt; ~/bin/wp ~/Pictures/wallpapers/&#xA;curl localhost:9876/next &lt;span class=&#34;z-c1&#34;&gt;# Next image&lt;&#x2F;span&gt;&#xA;curl localhost:9876/prev &lt;span class=&#34;z-c1&#34;&gt;# Previous image&lt;&#x2F;span&gt;&#xA;curl localhost:9876/blur &lt;span class=&#34;z-c1&#34;&gt;# Toggle blur&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;</description>
        </item>
        <item>
            <title>TrickleBar</title>
            <pubDate>Mon, 12 Oct 2026 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/tricklebar/</link>
            <guid>https://abdus.dev/projects/tricklebar/</guid>
            <description>&lt;div class=&#34;app-logo&#34;&gt;&lt;img src=&#34;./tricklebar-icon.svg&#34; alt=&#34;TrickleBar Logo&#34;&gt;&lt;&#x2F;div&gt;&#xA;&lt;style&gt;&#xA;.app-logo {&#xA;    width: 8rem;&#xA;    height: 8rem;&#xA;    margin-bottom: 2rem;&#xA;}&#xA;&lt;&#x2F;style&gt;&#xA;&lt;p&gt;TrickleBar is a menu app based on &lt;a href=&#34;https://aria2.github.io/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;aria2&lt;&#x2F;a&gt; that provides a minimal interface to view and manage your downloads.&#xA;It requires aria2 to be installed and available in &lt;code&gt;$PATH&lt;&#x2F;code&gt; or other common locations, but you can also override which aria2 binary to use. It has a minimal settings panel to configure aria2 options.&lt;&#x2F;p&gt;&#xA;&lt;image-gallery class=&#34;image-gallery&#34; role=&#34;region&#34; aria-label=&#34;tricklebar screenshots&#34;&gt;&#xA;    &lt;div class=&#34;image-gallery__track&#34; tabindex=&#34;0&#34; aria-label=&#34;Scroll to see more images and videos&#34;&gt;&#xA;        &lt;img src=&#34;./tricklebar-popup.png&#34; alt=&#34;tricklebar menu app screenshot&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;img src=&#34;./tricklebar-settings.png&#34; alt=&#34;tricklebar settings panel screenshot&#34; loading=&#34;lazy&#34;&gt;&#xA;    &lt;&#x2F;div&gt;&#xA;&lt;&#x2F;image-gallery&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;a class=&#34;repo-card&#34; href=&#34;https://github.com/abdusco/tricklebar&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&#xA;    &lt;span class=&#34;repo-card__name&#34;&gt;&#xA;        &lt;span class=&#34;repo-card__owner&#34;&gt;abdusco&lt;&#x2F;span&gt;&#xA;        &lt;span aria-hidden=&#34;true&#34;&gt;/&lt;&#x2F;span&gt;&#xA;        &lt;strong&gt;tricklebar&lt;&#x2F;strong&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__description&#34;&gt;A lightweight macOS menu bar download manager, backed by aria2c.&lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__meta&#34;&gt;&#xA;        &#xA;        &lt;span&gt;Swift&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span aria-label=&#34;1 stars&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-star&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            1&#xA;        &lt;&#x2F;span&gt;&#xA;        &lt;span aria-label=&#34;0 forks&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-fork&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;&lt;&#x2F;a&gt;&#xA;&#xA;&lt;p&gt;It also provides a CLI that allows you to add new downloads, and passes the extra switches to aria2c.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;bash&#34; data-highlighted=&#34;true&#34; class=&#34;language-bash z-code z-chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&amp;gt; TrickleBar.app/Contents/MacOS/TrickleBar --help&#xA;tricklebar — aria2c-compatible download manager&#xA;&#xA;Usage:&#xA;  tricklebar &lt;span class=&#34;z-o&#34;&gt;[&lt;&#x2F;span&gt;options&lt;span class=&#34;z-o&#34;&gt;]&lt;&#x2F;span&gt; URL...     Add download&lt;span class=&#34;z-o&#34;&gt;(&lt;&#x2F;span&gt;s&lt;span class=&#34;z-o&#34;&gt;)&lt;&#x2F;span&gt; to running daemon&#xA;  tricklebar -i FILE              Read URLs from file&#xA;  open TrickleBar.app             Launch menu bar interface&#xA;&#xA;Options &lt;span class=&#34;z-o&#34;&gt;(&lt;&#x2F;span&gt;subset of aria2c flags&lt;span class=&#34;z-o&#34;&gt;)&lt;&#x2F;span&gt;:&#xA;  -d, --dir&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;DIR                Download directory&#xA;  -o, --out&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;FILE               Output filename&#xA;  -s, --split&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;N                Connections per download&#xA;  -x, --max-connection-per-server&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;N&#xA;  ...&#xA;  -h, --help&#xA;  -v, --version&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;It listens to &lt;code&gt;tricklebar://&lt;&#x2F;code&gt; URLs to add downloads. You can even specify which script to run when a download is done. I use it to automatically move the downloaded file to the right folder and show a notification, for example.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;bash&#34; data-highlighted=&#34;true&#34; class=&#34;language-bash z-code z-chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;z-nv&#34;&gt;url&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$1&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;encoded&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;$(&lt;&#x2F;span&gt;python3 -c &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))&amp;#34;&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$url&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;)&lt;&#x2F;span&gt;&#xA;open &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;tricklebar://add-download?on_done=&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$HOME&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;/bin/aria2_on_done.py&amp;amp;url=&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$encoded&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;You can download the &lt;a href=&#34;https://github.com/abdusco/tricklebar/releases/latest&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;latest version&lt;&#x2F;a&gt; from GitHub.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>kuller</title>
            <pubDate>Sun, 11 Oct 2026 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/kuller/</link>
            <guid>https://abdus.dev/projects/kuller/</guid>
            <description>&lt;div class=&#34;app-logo&#34;&gt;&lt;img src=&#34;./kuller-icon.svg&#34; alt=&#34;kuller app icon&#34;&gt;&lt;&#x2F;div&gt;&#xA;&lt;style&gt;&#xA;.app-logo {&#xA;    width: 8rem;&#xA;    height: 8rem;&#xA;    margin-bottom: 2rem;&#xA;}&#xA;&lt;&#x2F;style&gt;&#xA;&lt;p&gt;kuller is an image culling app to quickly weed out bad photos and keep the best ones. It is designed to be minimal. It has a canvas window like Photoshop, and a side panel with a filmstrip of all the images in the current folder. You can quickly go through the images and mark them as keep or discard, or crop them in various aspect ratios using hotkeys.&lt;&#x2F;p&gt;&#xA;&lt;video&#xA;    src=&#34;.&amp;#x2F;kuller_demo.mp4&#34;&#xA;    poster=&#34;.&amp;#x2F;kuller_demo.jpg&#34;&#xA;    width=&#34;1626&#34;&#xA;    height=&#34;1080&#34;&#xA;    preload=&#34;none&#34;&#xA;    onclick=&#34;if (!this.played.length &amp;&amp; this.paused) this.play().catch(console.error)&#34;&#xA;    controls&#xA;    muted&#xA;    playsinline&#xA;&gt;&lt;&#x2F;video&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;a class=&#34;repo-card&#34; href=&#34;https://github.com/abdusco/kuller&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&#xA;    &lt;span class=&#34;repo-card__name&#34;&gt;&#xA;        &lt;span class=&#34;repo-card__owner&#34;&gt;abdusco&lt;&#x2F;span&gt;&#xA;        &lt;span aria-hidden=&#34;true&#34;&gt;/&lt;&#x2F;span&gt;&#xA;        &lt;strong&gt;kuller&lt;&#x2F;strong&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__description&#34;&gt;Minimal image culling app for macOS&lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__meta&#34;&gt;&#xA;        &#xA;        &lt;span&gt;Swift&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span aria-label=&#34;0 stars&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-star&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;        &lt;span aria-label=&#34;0 forks&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-fork&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;&lt;&#x2F;a&gt;&#xA;&#xA;&lt;p&gt;I built it to get ahead of my ever-growing pile of photos and I didn&#x27;t like Lightroom&#x27;s interface to do it. The first iteration was built as a web app backed by golang. I found myself using it quote often, so I decided to turn it into a native macOS app.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;It has a minimal interface, and a settings panel to customize the hotkeys. It offers a CLI interface which is how I use it.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;You can download &lt;a href=&#34;https://github.com/abdusco/kuller/releases/latest&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;the latest version&lt;&#x2F;a&gt; on GitHub.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>Testing database interactions in Golang</title>
            <pubDate>Sat, 10 Oct 2026 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/golang-database-tests/</link>
            <guid>https://abdus.dev/posts/golang-database-tests/</guid>
            <description>&lt;p&gt;You can have a test for a method that executes an SQL query, but it won&#x27;t tell you if the query is actually correct. Seeing a 100% coverage for it is a false assurance. You actually have to test your query against a real database.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;preparing-the-database&#34;&gt;Preparing the database&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;When I worked with Django, it offered a built-in test framework that &lt;a href=&#34;https://docs.djangoproject.com/en/dev/topics/testing/tools/#django.test.TransactionTestCase.fixtures&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;runs tests against a temporary database&lt;&#x2F;a&gt;. It created the database, ran the migrations, and then ran the tests. They could even be isolated within a transaction without causing bad interactions.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;For Django there was even a sidekick library called &lt;a href=&#34;https://factoryboy.readthedocs.io/en/stable/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;factory_boy&lt;&#x2F;a&gt;, that would hook into the ORM and create the right objects with the right relations and plug in fake data where needed. You&#x27;d be able to override a field nested inside a relation to make your assertions work.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-c1&#34;&gt;# this creates a person, &lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-c1&#34;&gt;# and a related address with the city set to &amp;#39;New York&amp;#39;.&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;p&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;PersonFactory&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;create&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;name&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;John Doe&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &#xA;    &lt;span class=&#34;z-n&#34;&gt;address__city&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;New York&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;assert&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;p&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;address&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;city&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;New York&amp;#34;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;There are some solutions for database testing in Golang, such as &lt;a href=&#34;https://github.com/DATA-DOG/go-sqlmock&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;sqlmock&lt;&#x2F;a&gt; or &lt;a href=&#34;https://github.com/testcontainers/testcontainers-go&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;testcontainers&lt;&#x2F;a&gt;, but they all feel clunky, cumbersome to use because of one thing: fixtures. How do you populate the database with the right data for your tests?&lt;&#x2F;p&gt;&#xA;&lt;p&gt;ORMs are not welcome in Golang. The language is not really built for dynamism. We write SQL statements directly without using an ORM, so the relations are only mapped in the developer&#x27;s head. We can accept this as a fact and build our harness around it.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/go-testfixtures/testfixtures&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;testfixtures&lt;&#x2F;a&gt; library helps with this. You write YAML files that describe which tables have which rows, and the library will insert those rows.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;yaml&#34; data-highlighted=&#34;true&#34; class=&#34;language-yaml z-code z-chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;z-nt&#34;&gt;persons&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;  &lt;&#x2F;span&gt;- &lt;span class=&#34;z-nt&#34;&gt;id&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-m&#34;&gt;1&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;name&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-l&#34;&gt;John Doe&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;address_id&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-m&#34;&gt;21&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;addresses&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;  &lt;&#x2F;span&gt;- &lt;span class=&#34;z-nt&#34;&gt;id&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-m&#34;&gt;21&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;city&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-l&#34;&gt;Berlin&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;street&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-l&#34;&gt;Boxhagenstr. 10&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We can build a test helper that populates the database with the right data and runs the queries against it.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;go&#34; data-highlighted=&#34;true&#34; class=&#34;language-go z-code z-chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;applyFixtures&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testing&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;DB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;sql&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;DB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;fixturePath&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-kt&#34;&gt;string&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Helper&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;fixtures&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testfixtures&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;New&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;        &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testfixtures&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Database&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;        &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testfixtures&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Dialect&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;postgres&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;        &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testfixtures&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Files&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;fixturePath&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;NoError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;fixtures&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Load&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;newTestDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testing&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;T&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;sql&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;DB&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// this is a database created from the latest seed&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;sql&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Open&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;postgres&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;user=postgres dbname=test sslmode=disable&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;NoError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Cleanup&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;        &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Close&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;})&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;TestGetUserAddress&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testing&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;T&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;newTestDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;applyFixtures&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;testdata/get_user_address.yaml&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;repo&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;NewRepo&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;userID&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-mi&#34;&gt;1&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// this refers to an ID in the fixture&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;addr&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;repo&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;GetUserAddress&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(),&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;userID&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;NoError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;assert&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Equal&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;Berlin&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;addr&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;City&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;isolating-tests&#34;&gt;Isolating tests&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;This is nice and all, but it leaves the database in a dirty state. Those rows remain in the database, and if we run multiple tests, they might interfere with each other. We need to wrap each test in a transaction and roll it back when the test ends.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/DATA-DOG/go-txdb&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;txdb&lt;&#x2F;a&gt; library fits well here.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;go&#34; data-highlighted=&#34;true&#34; class=&#34;language-go z-code z-chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;...&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#x9;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;github.com/DATA-DOG/go-txdb&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;_&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;github.com/lib/pq&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;init&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// we register an SQL driver named &amp;#34;txdb&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;txdb&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Register&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;txdb&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;postgres&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;user=postgres dbname=test sslmode=disable&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;newTestDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testing&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;T&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;sql&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;DB&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// this is a database created from the latest seed&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;realDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;sql&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Open&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;postgres&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;user=postgres dbname=test sslmode=disable&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;NoError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// apply the migrations to prepare it for tests&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;migrations&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Run&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(),&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;realDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;NoError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// use a dynamic name for the database connection to avoid conflicts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;sql&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Open&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;txdb&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Name&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;())&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;NoError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#x9;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Cleanup&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;        &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Close&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;})&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h3 id=&#34;dealing-with-the-setup-overhead&#34;&gt;Dealing with the setup overhead&lt;&#x2F;h3&gt;&#xA;&lt;p&gt;Running migrations for every test means the schema version is checked each time. The logic in &lt;code&gt;migrations.Run&lt;&#x2F;code&gt; hopefully skips over applied migrations, but still, it adds overhead across multiple packages. We need to run it once per test run, which includes all packages being tested.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We might reach for a &lt;code&gt;sync.Once&lt;&#x2F;code&gt;, but it won&#x27;t work as you&#x27;d expect. Golang compiles your tests and executes them in parallel as separate processes. So the &lt;code&gt;sync.Once&lt;&#x2F;code&gt; will only work for a single test process, not across all of them.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We need a global mutex to synchronize all those migrations. Postgres has our back.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;go&#34; data-highlighted=&#34;true&#34; class=&#34;language-go z-code z-chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;lock&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testing&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;T&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;sql&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;DB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Helper&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-kd&#34;&gt;const&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;lockID&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-mi&#34;&gt;1234567890&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// some unique number&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;_&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Exec&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;SELECT pg_advisory_lock($1)&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;lockID&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;NoError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;        &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;_&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Exec&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;SELECT pg_advisory_unlock($1)&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;lockID&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;        &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;NoError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;&lt;code&gt;lockID&lt;&#x2F;code&gt; is a hardcoded random number &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; that is shared across all runs.&#xA;This removes the need for a &lt;code&gt;sync.Once&lt;&#x2F;code&gt; and allows us to run migrations once per test run.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;go&#34; data-highlighted=&#34;true&#34; class=&#34;language-go z-code z-chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;migrateTestDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testing&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;T&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;sql&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;DB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;unlock&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;lock&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;defer&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;unlock&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// migrations will run once per test run.&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;migrations&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Run&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(),&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;NoError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;newTestDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;testing&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;T&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;sql&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;DB&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// this is a database created from the latest seed&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;realDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;sql&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;Open&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;postgres&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;user=postgres dbname=test sslmode=disable&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;NoError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;err&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;migrateTestDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;t&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;realDB&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;...&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;where-it-falls-short&#34;&gt;Where it falls short&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;This is a simplified version of what we use in production. However, it has some downsides.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;For one, the fixtures are not easy to prepare. Relations and values should make sense. Denormalized columns should be consistent across tables. We often end up with a lot of boilerplate data in the YAML files, because real-world tables don&#x27;t consist of a handful of columns — they have tens of columns. Queries often join many tables, so you have to specify the supporting rows for your query &lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Also, the queries are often not simple. If they have many &lt;code&gt;WHERE&lt;&#x2F;code&gt; clauses, it is not easy to prepare the &amp;quot;right&amp;quot; fixture that would satisfy the right combination of conditions. So you end up with huge fixtures that are difficult to maintain and reason about.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;The fixtures are also not reviewable, because you can&#x27;t put them next to the test code that uses them. You often end up referring to records by their IDs, which are not meaningful. The reviewer has to take the author&#x27;s word for it.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;This setup makes it straightforward to test your business logic that&#x27;s embedded in SQL queries against a real database. The harder part is maintaining the fixtures, keeping them small enough that the test&#x27;s intent remains clear.&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr /&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://xkcd.com/221/&#34;&gt;https://xkcd.com/221/&lt;&#x2F;a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;One way to reduce YAML bloat is to use YAML anchors, which allow you to define a table record once and reference it in multiple places. For example:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;yaml&#34; data-highlighted=&#34;true&#34; class=&#34;language-yaml z-code z-chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;z-nt&#34;&gt;addresses&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;  &lt;&#x2F;span&gt;- &lt;span class=&#34;z-cp&#34;&gt;&amp;amp;address_props&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;id&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-m&#34;&gt;21&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;city&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-l&#34;&gt;Berlin&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;street&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-l&#34;&gt;Boxhagenstr. 10&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c&#34;&gt;# imagine we have 10 more columns...&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;  &lt;&#x2F;span&gt;- &lt;span class=&#34;z-nt&#34;&gt;&amp;lt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-cp&#34;&gt;*address_props&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-c&#34;&gt;# reuse the fields from the first record, override the ID and city&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;id&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-m&#34;&gt;22&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;city&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-l&#34;&gt;Hamburg&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c&#34;&gt;# street and other columns are left out because they&amp;#39;re irrelevant for the test that references this address record.&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ol&gt;&#xA;&lt;&#x2F;div&gt;&#xA;</description>
        </item>
        <item>
            <title>htmlpopup</title>
            <pubDate>Sat, 10 Oct 2026 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/htmlpopup/</link>
            <guid>https://abdus.dev/projects/htmlpopup/</guid>
            <description>&lt;div class=&#34;htmlpopup-icon&#34;&gt;&lt;img src=&#34;./htmlpopup-icon.svg&#34; alt=&#34;htmlpopup app icon&#34;/&gt;&lt;&#x2F;div&gt;&#xA;&lt;style&gt;&#xA;.htmlpopup-icon {&#xA;    width: 8rem;&#xA;    height: 8rem;&#xA;    margin-bottom: 2rem;&#xA;}&#xA;&lt;&#x2F;style&gt;&#xA;&lt;p&gt;htmlpopup is a simple utility for macOS that allows you to open up an HTML page in a popup window. It provides a JS API to control the window, interact with the filesystem, returning values to the caller on exit etc. You can call it with a URL, or a local HTML file, or a directory, or an HTML literal or pipe HTML to it. It supports &lt;code&gt;localStorage&lt;&#x2F;code&gt; as well.&lt;&#x2F;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;a class=&#34;repo-card&#34; href=&#34;https://github.com/abdusco/htmlpopup&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&#xA;    &lt;span class=&#34;repo-card__name&#34;&gt;&#xA;        &lt;span class=&#34;repo-card__owner&#34;&gt;abdusco&lt;&#x2F;span&gt;&#xA;        &lt;span aria-hidden=&#34;true&#34;&gt;/&lt;&#x2F;span&gt;&#xA;        &lt;strong&gt;htmlpopup&lt;&#x2F;strong&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__description&#34;&gt;A simple macOS command-line utility to display HTML content or a URL in a native webview popup window.&lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__meta&#34;&gt;&#xA;        &#xA;        &lt;span&gt;Swift&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span aria-label=&#34;0 stars&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-star&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;        &lt;span aria-label=&#34;0 forks&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-fork&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;&lt;&#x2F;a&gt;&#xA;&#xA;&lt;p&gt;I built it to show basic UIs for other utilities, e.g. one for having an AI chat window, or a grammar checker, or a UI in front of a UI. You can inject environment variables and other data into the webview (accessible via &lt;code&gt;window.env.*&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;&#xA;&lt;p&gt;For example, this is how I use it for the AI chat:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;bash&#34; data-highlighted=&#34;true&#34; class=&#34;language-bash z-code z-chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;htmlpopup --id chat --env.PROMPT &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$1&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt; &lt;span class=&#34;z-se&#34;&gt;\&#xA;&lt;&#x2F;span&gt;  --env.GEMINI_API_KEY &lt;span class=&#34;z-nv&#34;&gt;$GEMINI_API_KEY&lt;&#x2F;span&gt; &lt;span class=&#34;z-se&#34;&gt;\&#xA;&lt;&#x2F;span&gt;  --env.OPENAI_API_KEY &lt;span class=&#34;z-nv&#34;&gt;$OPENAI_API_KEY&lt;&#x2F;span&gt; &lt;span class=&#34;z-se&#34;&gt;\&#xA;&lt;&#x2F;span&gt;  --env.ANTHROPIC_API_KEY &lt;span class=&#34;z-nv&#34;&gt;$ANTHROPIC_API_KEY&lt;&#x2F;span&gt; ~/dev/web/chat&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;where &lt;code&gt;$1&lt;&#x2F;code&gt; is the currently selected text (populated by Alfred on a hotkey press).&lt;&#x2F;p&gt;&#xA;&lt;p&gt;In another use case, I wrap it in python to use an API + HTML served by that script:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-nd&#34;&gt;@contextlib.contextmanager&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;run_htmlpopup&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;address&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;exe_path&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;shutil&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;which&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;htmlpopup&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;not&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;exe_path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;raise&lt;&#x2F;span&gt; &lt;span class=&#34;z-ne&#34;&gt;RuntimeError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;htmlpopup executable not found in PATH.&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Popen&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;([&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;exe_path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;--title&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;search&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;address&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;text&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PIPE&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;try&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;yield&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;wait&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;finally&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;terminate&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-c1&#34;&gt;# ...&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;with&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;api&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;serve&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;port&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;port&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;as&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;server_url&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;logging&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;info&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-sa&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;Server running at &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;server_url&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;with&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;run_htmlpopup&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;server_url&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;logging&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;info&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;HTML popup started. Press Ctrl+C to stop.&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Check out &lt;a href=&#34;https://github.com/abdusco/htmlpopup#readme&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;JS API documentation&lt;&#x2F;a&gt; for more details. Download the latest release from the &lt;a href=&#34;https://github.com/abdusco/htmlpopup/releases/latest&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Github releases page&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>Shimmer</title>
            <pubDate>Fri, 09 Oct 2026 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/shimmer/</link>
            <guid>https://abdus.dev/projects/shimmer/</guid>
            <description>&lt;div class=&#34;app-logo&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;300&#34; height=&#34;300&#34; version=&#34;1.2&#34; viewBox=&#34;0 0 1024 1024&#34;&gt;&lt;path d=&#34;M512 279c2 5 4 27 6 34a241 241 0 0 0 224 198c-75 8-143 42-186 105-27 40-37 79-44 125l-1-8a240 240 0 0 0-208-218l-25-4 34-5c39-7 76-23 107-47 42-33 72-78 85-130 4-17 6-32 8-50zM277 128l5 23c5 21 15 41 28 59 31 42 71 57 120 64-84 9-140 61-153 145l-1 4c-2-2-4-16-5-21a170 170 0 0 0-142-129 166 166 0 0 0 138-109c5-13 6-24 9-35zM749 601l5 23c19 74 71 111 142 126-64 8-117 51-139 112l-8 31c-9-43-20-74-54-103-31-26-58-35-97-40 86-12 135-65 151-149zM751 132l6 25c23 68 71 104 139 117-30 6-57 15-83 34-35 25-55 66-63 107l-1 6-1-1c-3-27-15-58-31-80-29-40-73-59-121-66 24-4 40-6 63-17 53-25 79-69 92-125zM275 601c2 1 6 25 7 29 21 72 74 108 146 120-31 2-66 16-91 35-35 26-55 65-61 107l-1 2-6-26a161 161 0 0 0-107-110l-33-8c82-14 135-65 146-149zM512 788l5 21c13 40 41 61 83 69-51 8-78 34-88 84-12-50-38-76-89-84 52-10 79-37 89-90zM878 426c2 3 3 15 5 20 4 11 10 22 17 31 16 19 38 32 63 34-26 8-42 12-60 33-16 17-20 30-25 52-12-52-41-76-92-85a98 98 0 0 0 91-84zM511 65c2 3 3 10 4 13 13 44 45 64 89 69-42 5-77 29-87 72l-5 22c-2-18-7-35-16-51-18-28-43-36-74-44 52-5 78-32 89-81zM146 427l4 14a101 101 0 0 0 82 70c-49 10-75 37-85 85l-5-16c-12-39-42-61-81-69 47-8 76-38 85-84z&#34;/&gt;&lt;&#x2F;svg&gt;&lt;&#x2F;div&gt;&#xA;&lt;style&gt;&#xA;.app-logo {&#xA;    width: 8rem;&#xA;    height: 8rem;&#xA;    margin-bottom: 2rem;&#xA;}&#xA;.app-logo svg {&#xA;    width: 100%;&#xA;    height: 100%;&#xA;    fill: var(--c-accent);&#xA;}&#xA;&lt;&#x2F;style&gt;&#xA;&lt;p&gt;Shimmer is a live wallpaper app for Android that lets you apply cool GPU-accelerated blur and duotone effects to your wallpapers.&lt;&#x2F;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;a class=&#34;repo-card&#34; href=&#34;https://github.com/abdusco/shimmer&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&#xA;    &lt;span class=&#34;repo-card__name&#34;&gt;&#xA;        &lt;span class=&#34;repo-card__owner&#34;&gt;abdusco&lt;&#x2F;span&gt;&#xA;        &lt;span aria-hidden=&#34;true&#34;&gt;/&lt;&#x2F;span&gt;&#xA;        &lt;strong&gt;shimmer&lt;&#x2F;strong&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__description&#34;&gt;A live wallpaper for Android that displays your photos with smooth GPU-based effects.&lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__meta&#34;&gt;&#xA;        &#xA;        &lt;span&gt;Kotlin&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span aria-label=&#34;9 stars&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-star&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            9&#xA;        &lt;&#x2F;span&gt;&#xA;        &lt;span aria-label=&#34;1 forks&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-fork&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            1&#xA;        &lt;&#x2F;span&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;&lt;&#x2F;a&gt;&#xA;&#xA;&lt;p&gt;I wanted to build this app ever since I first tried &lt;a href=&#34;https://muzei.co/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Muzei&lt;&#x2F;a&gt; back in 2014. I loved the idea of a wallpaper app just that gets out of your way. I didn&#x27;t know much about OpenGL, but with the help of AI, I was able to create one with GPU accelerated blur effects (with clever optimizations). It&#x27;s been a fun project to work on, and I&#x27;ve been fixing the bugs as I come across them.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;features&#34;&gt;Features&lt;&#x2F;h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;GPU accelerated gaussian blur and duotone effects.&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Chromatic aberration breathing effect on touch.&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Blur the wallpaper on lock, on timeout, or with a gesture.&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Many duotone color palettes to choose from, or you can pick your own colors.&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Cycle through your favorite wallpapers automatically on unlock or with a gesture.&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Marking wallpapers as favorites to easily find them later.&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Intent support for automation via Tasker or similar apps.&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;&lt;h2 id=&#34;download&#34;&gt;Download&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Currently only on Github, source if also available if you wanna build it yourself. I am not planning to put it on the Play Store, but you can download the APK from the &lt;a href=&#34;https://github.com/abdusco/shimmer/releases/latest&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;releases page&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>Prayer Times API v2</title>
            <pubDate>Thu, 08 Oct 2026 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/prayertimes-v2/</link>
            <guid>https://abdus.dev/projects/prayertimes-v2/</guid>
            <description>&lt;div class=&#34;prayertimes-logo&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;1em&#34; height=&#34;1em&#34; viewBox=&#34;0 0 24 24&#34;&gt;&#xA;&#x9;&lt;path d=&#34;M0 0h24v24H0z&#34; fill=&#34;none&#34; /&gt;&#xA;&#x9;&lt;path fill=&#34;currentColor&#34; d=&#34;M6.12 8h11.76m0 0c.07-.29.12-.59.12-.91c0-1.31-.65-2.53-1.74-3.25L12 1L7.74 3.84A3.89 3.89 0 0 0 6 7.09c0 .32.05.62.12.91&#34; /&gt;&#xA;&#x9;&lt;path fill=&#34;currentColor&#34; d=&#34;M24 7c0-1.1-2-3-2-3s-2 1.9-2 3c0 .74.4 1.38 1 1.72V13h-2V9H5v4H3V8.72c.6-.34 1-.98 1-1.72c0-1.1-2-3-2-3S0 5.9 0 7c0 .74.4 1.38 1 1.72V21h9v-6h4v6h9V8.72c.6-.34 1-.98 1-1.72&#34; /&gt;&#xA;&lt;&#x2F;svg&gt;&#xA;&lt;&#x2F;div&gt;&#xA;&lt;style&gt;&#xA;.prayertimes-logo {&#xA;    width: 8rem;&#xA;    height: 8rem;&#xA;    margin-bottom: 2rem;&#xA;}&#xA;.prayertimes-logo svg {&#xA;    width: 100%;&#xA;    height: 100%;&#xA;    color: var(--c-accent);&#xA;}&#xA;&lt;&#x2F;style&gt;&#xA;&lt;p&gt;I built &lt;a href=&#34;/projects/prayertimes/&#34;&gt;the first version of this API&lt;&#x2F;a&gt; back in 2020, and it has been chugging along since then.&#xA;However, it was based on scraping data from the &lt;a href=&#34;https://namazvakitleri.diyanet.gov.tr/tr-TR&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Presidency of Religious Affairs of Turkey&lt;&#x2F;a&gt; website, which was not very stable, and I even have a cronjob that restarts the server because it kept breaking and I didn&#x27;t have time to fix it.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;I started working on a newer version this year that functions by calculating the times based on the location and date. The calculations are based on studies published by Diyanet.&#xA;This also turns out to be how Diyanet&#x27;s own app works under the hood (I reverse engineered it). If you use the same coordinates as the app, you will get the exact same times. Slightly different coordinates will give slightly different times, but I think it&#x27;s acceptable.&#xA;It is also more stable and reliable than scraping the Diyanet website.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;I am using the city database from &lt;a href=&#34;https://www.geonames.org/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;GeoNames&lt;&#x2F;a&gt; that contains &lt;a href=&#34;https://download.geonames.org/export/dump/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;the most populous 5000 cities&lt;&#x2F;a&gt; to get the name + coordinates of a location, and then calculating the prayer times based on that.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;It also has &lt;a href=&#34;https://prayertimes.api.abdus.dev/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;a web UI&lt;&#x2F;a&gt; that also works as a PWA, so you can install it on your phone. The API docs are available &lt;a href=&#34;https://prayertimes.api.abdus.dev/api&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;&#xA;&#xA;&lt;a class=&#34;repo-card&#34; href=&#34;https://git.abdus.dev/abdus/prayertimes-go&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&#xA;    &lt;span class=&#34;repo-card__name&#34;&gt;&#xA;        &lt;span class=&#34;repo-card__owner&#34;&gt;abdus&lt;&#x2F;span&gt;&#xA;        &lt;span aria-hidden=&#34;true&#34;&gt;/&lt;&#x2F;span&gt;&#xA;        &lt;strong&gt;prayertimes-go&lt;&#x2F;strong&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__meta&#34;&gt;&#xA;        &#xA;        &lt;span&gt;Go&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span aria-label=&#34;0 stars&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-star&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;        &lt;span aria-label=&#34;0 forks&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-fork&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;&lt;&#x2F;a&gt;&#xA;&#xA;</description>
        </item>
        <item>
            <title>Sniffing io.Reader contents in Golang</title>
            <pubDate>Sun, 17 Jul 2022 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/sniffing-io-reader-in-golang/</link>
            <guid>https://abdus.dev/posts/sniffing-io-reader-in-golang/</guid>
            <description>&lt;h2 id=&#34;the-problem-reading-a-stream-multiple-times&#34;&gt;The problem: reading a stream multiple times&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;I wanted to use &lt;code&gt;ffmpeg&lt;&#x2F;code&gt; to generate contact sheet for a video. This is the command I use to generate a thumbnail tile.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;shell&#34; data-highlighted=&#34;true&#34; class=&#34;language-shell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;cat /path/to/video.mp4 &lt;span class=&#34;z-p&#34;&gt;|&lt;&#x2F;span&gt; ffmpeg &lt;span class=&#34;z-se&#34;&gt;\&#xA;&lt;&#x2F;span&gt;    -v error &lt;span class=&#34;z-se&#34;&gt;\&#xA;&lt;&#x2F;span&gt;    -skip_frame nokey &lt;span class=&#34;z-se&#34;&gt;\ &lt;&#x2F;span&gt; &lt;span class=&#34;z-c1&#34;&gt;# use keyframes only (makes it fast)&lt;&#x2F;span&gt;&#xA;    -ss &lt;span class=&#34;z-m&#34;&gt;10&lt;&#x2F;span&gt; &lt;span class=&#34;z-se&#34;&gt;\ &lt;&#x2F;span&gt; &lt;span class=&#34;z-c1&#34;&gt;# skip the first 10 seconds&lt;&#x2F;span&gt;&#xA;    -i - &lt;span class=&#34;z-se&#34;&gt;\&#xA;&lt;&#x2F;span&gt;    -vf &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;fps=1/60,scale=480:-1,tile=3x22&amp;#39;&lt;&#x2F;span&gt; &lt;span class=&#34;z-se&#34;&gt;\ &lt;&#x2F;span&gt; &lt;span class=&#34;z-c1&#34;&gt;# 480px wide tiles in 3 columns every 60 seconds&lt;&#x2F;span&gt;&#xA;    -frames &lt;span class=&#34;z-m&#34;&gt;1&lt;&#x2F;span&gt; &lt;span class=&#34;z-se&#34;&gt;\&#xA;&lt;&#x2F;span&gt;    -f image2pipe - &lt;span class=&#34;z-se&#34;&gt;\&#xA;&lt;&#x2F;span&gt;    &amp;gt; thumbs.jpg&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;The tricky part is to calculate &lt;code&gt;tile=3x22&lt;&#x2F;code&gt; argument. It&#x27;s the grid (and the number of tiles) that determines how far into the video we&#x27;re reading. If it&#x27;s too small, the contact sheet summarize the whole video. If it&#x27;s too many, the we get blank tiles at the end.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;There&#x27;s a way out: if we know the duration of the video, we can calculate how many tiles there should be, so that we can cover the whole video.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;For that, I use &lt;code&gt;ffprobe&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;shell&#34; data-highlighted=&#34;true&#34; class=&#34;language-shell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;cat /path/to/video.mp4 &lt;span class=&#34;z-p&#34;&gt;|&lt;&#x2F;span&gt; ffprobe &lt;span class=&#34;z-se&#34;&gt;\&#xA;&lt;&#x2F;span&gt;    -v quiet &lt;span class=&#34;z-se&#34;&gt;\&#xA;&lt;&#x2F;span&gt;    -i - &lt;span class=&#34;z-se&#34;&gt;\&#xA;&lt;&#x2F;span&gt;    -print_format json &lt;span class=&#34;z-se&#34;&gt;\&#xA;&lt;&#x2F;span&gt;    -show_entries &lt;span class=&#34;z-nv&#34;&gt;format&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;duration&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;which returns the duration, and we can use that to calculate the size of the tile grid, for a given interval.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;format&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;duration&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;4059.051000&amp;#34;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;I call &lt;code&gt;ffmpeg&lt;&#x2F;code&gt; from a CLI I&#x27;ve written in golang. I wanted to pipe the video stream to both &lt;code&gt;ffprobe&lt;&#x2F;code&gt; and &lt;code&gt;ffmpeg&lt;&#x2F;code&gt;, and reuse it without the caller knowing.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;golang&#34; data-highlighted=&#34;true&#34; class=&#34;language-golang z-code z-chroma&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;func&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;generateThumbs&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;ctx&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;Context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;r&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;io&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;Reader&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;w&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;io&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;Writer&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-kt&#34;&gt;error&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;cmd&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;exec&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;CommandContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;ctx&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;ffprobe&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt;/*...*/&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;cmd&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;Stdin&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;r&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// parse duration&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// use the duration to calculate ffmpeg args&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;cmd&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;exec&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;CommandContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;ctx&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;ffmpeg&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt;/*...*/&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;cmd&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;Stdin&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;r&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;  &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// reuse the video stream?&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;cmd&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;Stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;w&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now we face a problem: whatever &lt;code&gt;ffprobe&lt;&#x2F;code&gt; reads from the video stream &lt;code&gt;r&lt;&#x2F;code&gt; is gone and &lt;code&gt;ffmpeg&lt;&#x2F;code&gt; cannot read it.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We need to find a way to sniff the video properties and put what we consume back where we found it.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;solution-io-teereader-and-io-multireader&#34;&gt;Solution: &lt;code&gt;io.TeeReader&lt;&#x2F;code&gt; and &lt;code&gt;io.MultiReader&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Luckily, &lt;code&gt;ffprobe&lt;&#x2F;code&gt; doesn&#x27;t need to read the whole file to determine the video properties. It only reads a few MBs from the beginning. It doesn&#x27;t hurt to keep a couple of MBs of video in memory.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We can use &lt;code&gt;io.TeeReader&lt;&#x2F;code&gt; to keep those bits in a buffer. Then use a &lt;code&gt;io.MultiReader&lt;&#x2F;code&gt; to reconstruct the original stream by combining the buffer &lt;code&gt;buf&lt;&#x2F;code&gt; with the remainder of the input stream &lt;code&gt;r&lt;&#x2F;code&gt;.&#xA;Then we pipe this to &lt;code&gt;ffmpeg&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;golang&#34; data-highlighted=&#34;true&#34; class=&#34;language-golang z-code z-chroma&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;var&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;buf&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;bytes&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;Buffer&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;  &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// keep the bits ffprobe needs in a buffer&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;tr&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;io&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;TeeReader&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;r&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;buf&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;cmd&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;exec&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;CommandContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;ctx&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;ffprobe&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt;/*...*/&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;cmd&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;Stdin&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;tr&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;  &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// use the tee reader&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;r&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;io&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;MultiReader&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;buf&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;r&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;  &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// then combine it back (in the right order)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;cmd&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;exec&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nf&#34;&gt;CommandContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;ctx&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;ffmpeg&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt;/*...*/&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;cmd&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;Stdin&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;r&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;  &lt;&#x2F;span&gt;&lt;span class=&#34;z-c1&#34;&gt;// re-constructed input&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now the contraption works great. &lt;code&gt;ffprobe&lt;&#x2F;code&gt; reads a couple megs, finds the duration, then ffmpeg reads the whole file again and generates the thumbs.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>Using Django ORM without the framework</title>
            <pubDate>Thu, 21 Oct 2021 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/django-orm-standalone/</link>
            <guid>https://abdus.dev/posts/django-orm-standalone/</guid>
            <description>&lt;p&gt;Django&#x27;s ORM is really simple to use and migrations support is great. When I modify a model and run &lt;code&gt;makemigrations&lt;&#x2F;code&gt; it generates a migration from the changes, and I can apply it with &lt;code&gt;migrate&lt;&#x2F;code&gt; and I&#x27;m done.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;I wanted to use it in a serverless function to write in a remote Postgres instance. Django doesn&#x27;t have a separate package for it&#x27;s ORM, but it&#x27;s still possible to use it on its own.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;minimal-boilerplate&#34;&gt;Minimal boilerplate&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;First, I&#x27;ve created a &lt;code&gt;manage.py&lt;&#x2F;code&gt; to configure Django. It also runs the management CLI.&#xA;I only need to specify the &lt;code&gt;INSTALLED_APPS&lt;&#x2F;code&gt; for model discovery to work, and a database connection.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-ch&#34;&gt;#!/usr/bin/env python&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;init_django&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;django&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;django.conf&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;settings&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;settings&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;configured&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-n&#34;&gt;settings&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;configure&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;INSTALLED_APPS&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;db&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;DATABASES&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;default&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;ENGINE&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;django.db.backends.postgresql&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;NAME&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;myapp&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;USER&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;myapp_user&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;PASSWORD&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;myapp&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;HOST&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;127.0.0.1&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;PORT&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;5432&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;django&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;setup&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;__main__&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;django.core.management&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;execute_from_command_line&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-n&#34;&gt;init_django&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;execute_from_command_line&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;I&#x27;ve created a module called &lt;code&gt;db&lt;&#x2F;code&gt; to act as a Django app and placed a &lt;code&gt;models.py&lt;&#x2F;code&gt; in it.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-c1&#34;&gt;# db/models.py&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;django.db&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;models&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;manage&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;init_django&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-n&#34;&gt;init_django&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;Model&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;models&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Model&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nb&#34;&gt;id&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;models&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AutoField&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;primary_key&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;created_at&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;models&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;DateTimeField&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;auto_now_add&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;db_index&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;updated_at&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;models&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;DateTimeField&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;auto_now&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;Meta&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;abstract&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-c1&#34;&gt;# define models here&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;Post&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Model&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-o&#34;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Django needs to initialized for models to work, that&#x27;s why I call &lt;code&gt;init_django()&lt;&#x2F;code&gt; to initialize it before defining the models.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Here&#x27;s how the final folder structure looks like:&lt;&#x2F;p&gt;&#xA;&lt;pre&gt;&lt;code&gt;.&#xA;|-- db&#xA;|   |-- __init__.py&#xA;|   `-- models.py&#xA;|-- manage.py&#xA;|-- requirements.txt&#xA;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;That&#x27;s about it. Now I can build models and update the database schema:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;shell&#34; data-highlighted=&#34;true&#34; class=&#34;language-shell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;z-c1&#34;&gt;# create a migration under `./db/migrations`&lt;&#x2F;span&gt;&#xA;$ python manage.py makemigrations db &#xA;&#xA;&lt;span class=&#34;z-c1&#34;&gt;# apply migrations&lt;&#x2F;span&gt;&#xA;$ python manage.py migrate&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;To use the models, I import them directly like any other class.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;db.models&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Post&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;it&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Post&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;objects&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;all&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;it&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;</description>
        </item>
        <item>
            <title>Authenticating a user with multiple social providers simultaneously in ASP.NET Core</title>
            <pubDate>Thu, 29 Jul 2021 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/aspnetcore-multiple-sessions/</link>
            <guid>https://abdus.dev/posts/aspnetcore-multiple-sessions/</guid>
            <description>&lt;p&gt;Assume we have an app that gives users the option to &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;login with Google, GitHub, Facebook, etc.&lt;&#x2F;a&gt;. We want to let users login with any identity provider and save their session to access the claims they provide later.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;For example, if I login with Google, it returns claims about the logged-in user, such as his name, email, account id. The same holds true for GitHub and all other OIDC providers.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;problem&#34;&gt;Problem&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;If the user signs in with his Google account, then signs in with GitHub, the session cookie for Google is overwritten by that of GitHub&#x27;s.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;How can we access all active sessions and read the claims?&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;setting-up-authentication-for-google-and-github&#34;&gt;Setting up authentication for Google and GitHub&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;We define the authentication schemes like this:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddAuthentication&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;DefaultScheme&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CookieAuthenticationDefaults&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AuthenticationScheme&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddCookie&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddGoogle&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-c1&#34;&gt;// set the client id + secret&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;Configuration&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetSection&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;Google&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;).&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Bind&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;})&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddGitHub&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-c1&#34;&gt;// set the client id + secret&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;Configuration&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetSection&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;GitHub&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;).&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Bind&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;});&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddAuthorization&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We use a cookie to hold the user session, and a couple of external id providers to offer the users. The credentials are stored in &lt;code&gt;appsettings.json&lt;&#x2F;code&gt; in this format, so they play nice&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; with the &lt;code&gt;IConfiguration.Bind()&lt;&#x2F;code&gt; method:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;GitHub&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;ClientId&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;c27966ecf4087378&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;ClientSecret&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;66c71cdac566e43d869dd3c70318ed5e8df22af7a565f068adef6a6bacf6aca7&amp;#34;&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;Google&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;ClientId&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;ed5a20d944fd12ebe33ad0de.apps.googleusercontent.com&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;ClientSecret&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;87a3a35d003d1622c6bdbf454c0e3608ca68b1e780c965df4c52dc2f9b2c9d78&amp;#34;&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now when we &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-5.0#challenge&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;issue a challenge&lt;&#x2F;a&gt;, the user is forced to login with a provider:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-na&#34;&gt;[ApiController]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;AccountController&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ControllerBase&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;    [AllowAnonymous]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;    [HttpGet(&amp;#34;/login-google&amp;#34;)]&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ActionResult&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;LoginGoogle&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Challenge&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;AuthenticationProperties&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;RedirectUri&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Url&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Action&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;WhoAmI&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;GoogleDefaults&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AuthenticationScheme&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-na&#34;&gt;    [AllowAnonymous]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;    [HttpGet(&amp;#34;/login-GitHub&amp;#34;)]&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ActionResult&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;LoginGitHub&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Challenge&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;AuthenticationProperties&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;RedirectUri&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Url&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Action&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;WhoAmI&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;GitHubAuthenticationDefaults&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AuthenticationScheme&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Visiting &lt;code&gt;/login-google&lt;&#x2F;code&gt;, the app redirects us to Google sign in page, and after logging in, we are redirected back&#xA;to &lt;code&gt;WhoAmI&lt;&#x2F;code&gt; action, which dumps all user claims.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-na&#34;&gt;[AllowAnonymous]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;[HttpGet(&amp;#34;/me&amp;#34;)]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ActionResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;WhoAmI&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-c1&#34;&gt;// dump all claims&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Ok&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;User&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Identities&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Select&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;id&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                    &lt;span class=&#34;z-n&#34;&gt;id&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AuthenticationType&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                    &lt;span class=&#34;z-n&#34;&gt;Claims&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;id&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Claims&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Select&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;c&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;c&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Type&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;c&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Value&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;})&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ToList&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;The result is an empty JSON:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;authenticationType&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;null&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;claims&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[]&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;&lt;code&gt;WhoAmI&lt;&#x2F;code&gt; action doesn&#x27;t do anything, because we&#x27;re bypassing authentication with &lt;code&gt;[AllowAnonymous]&lt;&#x2F;code&gt;. We need to add &lt;code&gt;[Authorize]&lt;&#x2F;code&gt; attribute&#xA;and specify an authentication scheme to access the claims for that session.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-na&#34;&gt;[Authorize(AuthenticationSchemes = GoogleDefaults.AuthenticationScheme)]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;[HttpGet(&amp;#34;/me&amp;#34;)]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ActionResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;WhoAmI&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-c1&#34;&gt;// `User` now contains Google claims&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;disappearing-sessions&#34;&gt;Disappearing sessions&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Now if we log in with GitHub by visiting &lt;code&gt;/login-github&lt;&#x2F;code&gt;, and sign in with our credentials, we&#x27;re redirected to &lt;code&gt;/me&lt;&#x2F;code&gt;,&#xA;but it keeps redirecting us to Google&#x27;s login page. What is happening here?&lt;&#x2F;p&gt;&#xA;&lt;p&gt;When we&#x27;ve signed in on GitHub, the session is saved to the cookie we&#x27;ve added with &lt;code&gt;.AddCookie()&lt;&#x2F;code&gt;, since it is the&#xA;default authentication scheme. And unless specified otherwise, all authentication actions default to the &lt;code&gt;DefaultScheme&lt;&#x2F;code&gt;&#xA;.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddAuthentication&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;DefaultScheme&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CookieAuthenticationDefaults&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AuthenticationScheme&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddCookie&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;To prevent losing our Google session, we need a separate cookie to store it. Let&#x27;s add another one and give both a&#xA;better name while we&#x27;re at it. Then we use those cookies to store Google &amp;amp; GitHub sessions:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddAuthentication&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddCookie&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;GoogleSession&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddCookie&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;GitHubSession&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddGoogle&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// save session to this cookie&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;SignInScheme&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s&#34;&gt;&amp;#34;GoogleSession&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;})&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddGitHub&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// save session to this cookie&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;SignInScheme&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s&#34;&gt;&amp;#34;GitHubSession&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;});&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now &lt;code&gt;WhoAmI&lt;&#x2F;code&gt; action works as expected and we get a list of Google claims thanks to &lt;code&gt;[Authorize]&lt;&#x2F;code&gt; attribute:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;authenticationType&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;Google&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;claims&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;type&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;value&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;13131323423131413123&amp;#34;&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;type&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;value&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;Abdussamet Koçak&amp;#34;&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-c1&#34;&gt;// ...&#xA;&lt;&#x2F;span&gt;    &lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;If we change the &lt;code&gt;[Authorize]&lt;&#x2F;code&gt; attribute to &lt;code&gt;GitHub&lt;&#x2F;code&gt;, we get GitHub claims. But not both. For that we need to specify the names of both separated with a comma:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-na&#34;&gt;[Authorize(AuthenticationSchemes = &amp;#34;Google,GitHub&amp;#34;)]&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We can&#x27;t concatenate the constants &lt;code&gt;GoogleDefaults.AuthenticationScheme&lt;&#x2F;code&gt;&#xA;and &lt;code&gt;GitHubAuthenticationDefaults.AuthenticationScheme&lt;&#x2F;code&gt; here, because C# doesn&#x27;t support concatenated constants (yet,&#xA;but &lt;a href=&#34;https://GitHub.com/dotnet/csharplang/issues/2951&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;it&#x27;s on its way to C# 10&lt;&#x2F;a&gt;), so instead, we&#x27;re using their values.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Now &lt;code&gt;/me&lt;&#x2F;code&gt; returns claims for both sessions:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;authenticationType&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;GitHub&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;claims&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-c1&#34;&gt;// ... GitHub claims&#xA;&lt;&#x2F;span&gt;    &lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;authenticationType&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;Google&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;claims&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-c1&#34;&gt;// ... google claims&#xA;&lt;&#x2F;span&gt;    &lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;setting-a-default-authorization-policy&#34;&gt;Setting a default authorization policy&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Specifying all these magic values by hand is tedious. Luckily ASP.NET Core gives us some other options to achieve the same result.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;After configuring a default authorization policy that forces the user authenticate with multiple schemes,&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddAuthorization&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;DefaultPolicy&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;AuthorizationPolicyBuilder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;GoogleDefaults&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AuthenticationScheme&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;GitHubAuthenticationDefaults&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AuthenticationScheme&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;RequireAuthenticatedUser&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Build&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;we can get rid of auth the arguments in &lt;code&gt;[Authorize]&lt;&#x2F;code&gt; and use the default policy.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-na&#34;&gt;[Authorize]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;[HttpGet(&amp;#34;/me&amp;#34;)]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ActionResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;WhoAmI&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;...&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;resources&#34;&gt;Resources&lt;&#x2F;h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/security/authentication/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Microsoft&#x27;s docs on getting started with ASP.NET Core auth&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Microsoft&#x27;s docs on setting up social authentication&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr /&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Keys in the &lt;code&gt;appsettings.json&lt;&#x2F;code&gt; configuration file correspond to the properties in the option class of their&#xA;respective auth schemes.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ol&gt;&#xA;&lt;&#x2F;div&gt;&#xA;</description>
        </item>
        <item>
            <title>Adding a default timeout to CancellationToken parameters in ASP.NET Core</title>
            <pubDate>Tue, 29 Jun 2021 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/aspnetcore-inject-cancellationtoken-with-timeout/</link>
            <guid>https://abdus.dev/posts/aspnetcore-inject-cancellationtoken-with-timeout/</guid>
            <description>&lt;p&gt;ASP.NET Core allows us to inject a &lt;code&gt;CancellationToken&lt;&#x2F;code&gt; in actions:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-na&#34;&gt;[HttpGet]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ActionResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;GetLoggedInUser&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CancellationToken&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;cancellationToken&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;...&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;This cancellation token is actually bound to &lt;code&gt;HttpContext.RequestAborted&lt;&#x2F;code&gt;, and it is signalled when the request is cancelled.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We can flow this token down to any async method that accepts it and help coordinate their cancellation if the request is cancelled by the user (due to page navigation, for example).&#xA;This prevents wasting time doing heavy work, and having to throw it all away for a client that&#x27;s already gone.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;problem&#34;&gt;Problem&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;How can we add a default timeout to this token, so that the operation is cancelled either when the request is cancelled or it takes too long?&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;model-binding&#34;&gt;Model binding&lt;&#x2F;h2&gt;&#xA;&lt;div class=&#34;has-recommend&#34;&gt;&#xA;&lt;p&gt;MVC framework performs model binding on action parameters by parsing the request into usable data structures.&#xA;It is used for reading the request body serialized as JSON to a class, or extracting a form field, or reading file stream into an &lt;code&gt;IFormFile&lt;&#x2F;code&gt;, among other things.&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;recommend&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;/posts/aspnetcore-model-binding-json-query-params/&#34;&gt;Model binding for query parameters encoded as JSON in ASP.NET Core&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;div&gt;&#xA;&lt;&#x2F;div&gt;&#xA;&lt;p&gt;&lt;code&gt;CancellationToken&lt;&#x2F;code&gt;s are bound to &lt;code&gt;HttpContext.RequestAborted&lt;&#x2F;code&gt; by &lt;code&gt;CancellationTokenModelBinder&lt;&#x2F;code&gt; class.&#xA;We&#x27;ll replace it with our own implementation that will set the token to expire with a timeout.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;But first, how do we join cancellation tokens together?&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;combining-cancellation-tokens&#34;&gt;Combining cancellation tokens&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;We can merge multiple cancellation tokens into one with &lt;code&gt;CancellationTokenSource.CreateLinkedTokenSource&lt;&#x2F;code&gt; method.&#xA;Here we create a timeout token, and link it to another:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;cancellationToken&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;timeoutCts&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CancellationTokenSource&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;timeoutCts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CancelAfter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;TimeSpan&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;FromSeconds&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-m&#34;&gt;10&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;));&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;combinedCts&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CancellationTokenSource&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CreateLinkedTokenSource&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;timeoutCts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Token&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;cancellationToken&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Time to put it to use.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;building-a-model-binder-for-cancellation-tokens&#34;&gt;Building a model binder for cancellation tokens&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Looking at the source code for &lt;code&gt;CancellationTokenModelBinder&lt;&#x2F;code&gt;, it doesn&#x27;t do much, but we&#x27;ll still use it as our starting point.&#xA;We&#x27;ll subclass it, but with a twist. Because it doesn&#x27;t mark &lt;code&gt;BindModelAsync&lt;&#x2F;code&gt; method as &lt;code&gt;virtual&lt;&#x2F;code&gt; we can&#x27;t override it with an &lt;code&gt;override&lt;&#x2F;code&gt; keyword.&#xA;By implementing the &lt;code&gt;IModelBinder&lt;&#x2F;code&gt; interface that the base class implements, &lt;a href=&#34;/posts/csharp-overriding-non-virtual-methods/&#34;&gt;we can get over this limitation&lt;&#x2F;a&gt; and still &amp;quot;override&amp;quot; it without actually using &lt;code&gt;override&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;After letting the base class perform the binding, we&#x27;ll take the bound token, and replace it with a new one.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Since we&#x27;ve replaced the token object, we&#x27;ll have to rebuild the validation state. In fact, why not just copy over what ASP.NET Core team has put there&#xA;because I don&#x27;t know enough about to disagree with them.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;private&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;TimeoutCancellationTokenModelBinder&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CancellationTokenModelBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IModelBinder&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;BindModelAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelBindingContext&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;await&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;base&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BindModelAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Result&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Model&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;is&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CancellationToken&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;cancellationToken&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-c1&#34;&gt;// combine the default token with a timeout&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;timeoutCts&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CancellationTokenSource&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;timeoutCts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CancelAfter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;TimeSpan&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;FromSeconds&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-m&#34;&gt;5&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;));&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;combinedCts&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CancellationTokenSource&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CreateLinkedTokenSource&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;timeoutCts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Token&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;cancellationToken&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&#xA;            &lt;span class=&#34;z-c1&#34;&gt;// We need to force boxing now, so we can insert the same reference to the boxed CancellationToken&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-c1&#34;&gt;// in both the ValidationState and ModelBindingResult.&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-c1&#34;&gt;//&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-c1&#34;&gt;// DO NOT simplify this code by removing the cast.&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;model&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kt&#34;&gt;object&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;combinedCts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Token&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ValidationState&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Clear&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ValidationState&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Add&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;model&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ValidationStateEntry&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;SuppressValidation&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;true&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;});&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Result&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ModelBindingResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Success&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;model&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We also need a provider to introduce this model binder to MVC pipeline:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;TimeoutCancellationTokenModelBinderProvider&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IModelBinderProvider&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IModelBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;?&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;GetBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelBinderProviderContext&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;?.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Metadata&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelType&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;!=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;typeof&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CancellationToken&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;null&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;TimeoutCancellationTokenModelBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;private&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;TimeoutCancellationTokenModelBinder&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CancellationTokenModelBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IModelBinder&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;replacing-model-binders&#34;&gt;Replacing model binders&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;MVC has &lt;a href=&#34;https://github.com/dotnet/aspnetcore/blob/9fa3421e1302bce7dbc50269edbd3072907e7832/src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs#L62-L78&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;many model binders in place&lt;&#x2F;a&gt; to handle all kinds of parsing and binding for various types of parameters and sources.&#xA;We need to put ours in the first place to run it before all others. We&#x27;ll also remove the original cancellation token binder&#xA;in order to prevent both binders from working over themselves.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Configure&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;MvcOptions&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelBinderProviders&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;RemoveType&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CancellationTokenModelBinderProvider&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;();&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelBinderProviders&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Insert&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-m&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;TimeoutCancellationTokenModelBinderProvider&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;());&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;});&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Let&#x27;s put it to practice. We&#x27;ll try to run a long async task that takes 6 seconds.&#xA;The &lt;code&gt;CancellationToken&lt;&#x2F;code&gt; parameter will be bound by our binder and it is set to expire after 5 seconds.&#xA;After 5 seconds, it will throw an &lt;code&gt;TaskCanceledException&lt;&#x2F;code&gt; and cancel the execution.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-na&#34;&gt;[HttpGet(&amp;#34;&amp;#34;)]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IActionResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Index&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CancellationToken&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;cancellationToken&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;await&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Delay&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;TimeSpan&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;FromSeconds&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-m&#34;&gt;6&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;cancellationToken&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Ok&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;hey&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now let&#x27;s get rid of the hardcoded values.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;making-it-configurable&#34;&gt;Making it configurable&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;We&#x27;ve set the timeout to 5 seconds, but it&#x27;d be better if it were configurable. We&#x27;ll use the &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;options pattern&lt;&#x2F;a&gt; for this.&#xA;Let&#x27;s create a class to encapsulate the options.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;TimeoutOptions&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-kt&#34;&gt;int&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;TimeoutSeconds&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;get&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;set&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-m&#34;&gt;10&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt; &lt;span class=&#34;z-c1&#34;&gt;// seconds&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;TimeSpan&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Timeout&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;TimeSpan&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;FromSeconds&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;TimeoutSeconds&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We can configure the timeout in &lt;code&gt;ConfigureServices&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Configure&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;TimeoutOptions&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;configuration&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;configuration&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;TimeoutSeconds&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-m&#34;&gt;10&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;});&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;and take this as a parameter in the binder provider.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;TimeoutCancellationTokenModelBinderProvider&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IModelBinderProvider&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IModelBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;?&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;GetBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelBinderProviderContext&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// resolve the configuration from the container&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;config&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetRequiredService&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IOptions&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;TimeoutOptions&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&amp;gt;().&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Value&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// pass it down to the binder&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;TimeoutCancellationTokenModelBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;config&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;private&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;TimeoutCancellationTokenModelBinder&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CancellationTokenModelBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IModelBinder&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// then inject it to the binder&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-kd&#34;&gt;private&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;readonly&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;TimeoutOptions&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;_options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;TimeoutCancellationTokenModelBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;TimeoutOptions&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;_options&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;BindModelAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelBindingContext&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt;/* ... */&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-c1&#34;&gt;// use the configured timeout value&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;timeoutCts&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CancellationTokenSource&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;timeoutCts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CancelAfter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Timeout&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;That&#x27;s it. Thanks for reading.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>Overriding non-virtual methods in C#</title>
            <pubDate>Mon, 28 Jun 2021 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/csharp-overriding-non-virtual-methods/</link>
            <guid>https://abdus.dev/posts/csharp-overriding-non-virtual-methods/</guid>
            <description>&lt;p&gt;C# lets us add &lt;code&gt;virtual&lt;&#x2F;code&gt; modifier to a method to indicate that it can be overridden by the subclasses of that (non-&lt;code&gt;sealed&lt;&#x2F;code&gt;) class.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;problem&#34;&gt;Problem&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/override&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;C# spec says&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;You cannot override a non-virtual or static method. The overridden base method must be &lt;code&gt;virtual&lt;&#x2F;code&gt;, &lt;code&gt;abstract&lt;&#x2F;code&gt;, or &lt;code&gt;override&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;blockquote&gt;&#xA;&lt;p&gt;When working with a library, sometimes we &lt;em&gt;really&lt;&#x2F;em&gt; need to override a method to plug our own logic, but darn it, it&#x27;s not &lt;code&gt;virtual&lt;&#x2F;code&gt;, definitely not &lt;code&gt;abstract&lt;&#x2F;code&gt;, it doesn&#x27;t have &lt;code&gt;override&lt;&#x2F;code&gt; either. What do we do?&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;trick&#34;&gt;Trick&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;We have one crucial condition for this trick to work: &lt;strong&gt;the method we want to override must come from an interface implemented by the class.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Take this example:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-k&#34;&gt;interface&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;IEmailSender&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;SendResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;SendAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;MailMessage&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;email&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;DefaultEmailSender&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IEmailSender&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;SendResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;SendAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;MailMessage&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;email&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We want to override the &lt;code&gt;SendAsync&lt;&#x2F;code&gt; method, but it&#x27;s not &lt;code&gt;virtual&lt;&#x2F;code&gt;/&lt;code&gt;override&lt;&#x2F;code&gt;. Don&#x27;t fret, in three easy steps, we can override it:&lt;&#x2F;p&gt;&#xA;&lt;ol start=&#34;0&#34;&gt;&#xA;&lt;li&gt;Create a subclass&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Implement the interface that provides the method&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Add &lt;code&gt;new&lt;&#x2F;code&gt; modifier&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ol&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;ConsoleEmailSender&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;DefaultEmailSender&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IEmailSender&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;SendResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;SendAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;MailMessage&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;email&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// custom logic!&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We can modify the arguments, call the base implementation and modify the result:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;ConsoleEmailSender&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;DefaultEmailSender&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IEmailSender&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;SendResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;SendAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;MailMessage&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;email&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// ... modify the arguments&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-n&#34;&gt;Console&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;WriteLine&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;email&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Subject&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;email&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Subject&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s&#34;&gt;&amp;#34;console&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;result&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;await&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;base&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;SendAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// ... modify the results&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;result&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;It is a neat little trick that I resort to from time to time.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>Model binding for query parameters encoded as JSON in ASP.NET Core</title>
            <pubDate>Tue, 30 Mar 2021 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/aspnetcore-model-binding-json-query-params/</link>
            <guid>https://abdus.dev/posts/aspnetcore-model-binding-json-query-params/</guid>
            <description>&lt;p&gt;I&#x27;ve been building and an API that supports seaching with complex filters. Think of product filtering you see on most shopping websites, with inputs for price, rating, brand and so on.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;By convention, search APIs are exposed from a &lt;code&gt;GET&lt;&#x2F;code&gt; endpoint, and take parameters as query strings. Complex nested objects are usually serialized in bracket notation i.e. &lt;code&gt;?field[subfield]&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;http&#34; data-highlighted=&#34;true&#34; class=&#34;language-http z-code z-chroma&#34;&gt;&lt;code class=&#34;language-http&#34; data-lang=&#34;http&#34;&gt;&lt;span class=&#34;z-err&#34;&gt;GET /products?&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;  filter[title][contains]=ssd&amp;amp;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;  filter[price][lt]=100&amp;amp;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;  sort[field]=price&amp;amp;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;  sort[direction]=ASC&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;This is unnecessarily verbose. Field path prefixes are repeated multiple times. ASP.NET Core doesn&#x27;t seem to support this bracketed format for binding complex types, either.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;I prefer querying the API with arguments serialized as JSON. It&#x27;s more succinct, and also easier for clients to serialize.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;http&#34; data-highlighted=&#34;true&#34; class=&#34;language-http z-code z-chroma&#34;&gt;&lt;code class=&#34;language-http&#34; data-lang=&#34;http&#34;&gt;&lt;span class=&#34;z-err&#34;&gt;GET /products?query=&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;  {&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;    &amp;#34;filter&amp;#34;: {&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;      &amp;#34;title&amp;#34;: {&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;        &amp;#34;contains&amp;#34;: &amp;#34;ssd&amp;#34;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;      },&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;    },&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;    &amp;#34;sort&amp;#34;: {&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;      &amp;#34;field&amp;#34;: &amp;#34;price&amp;#34;,&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;      &amp;#34;direction&amp;#34;: &amp;#34;ASC&amp;#34;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;    }&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;  }&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;With ASP.NET Core, we get neither of them.&#xA;To illustrate it, let&#x27;s create an action that accepts a &lt;code&gt;SearchQuery&lt;&#x2F;code&gt; instance, populated from the query string:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-na&#34;&gt;[HttpGet]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ActionResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;SearchResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;SearchProducts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;    [FromQuery]&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;SearchQuery&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;query&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;...&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;When we inspect the OpenAPI / Swagger schema, the action is described as having separate parameters for each property in my query type.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;paths&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;/api/products&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;get&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;parameters&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;          &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;name&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;Filter.Title.Contains&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;in&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;query&amp;#34;&lt;&#x2F;span&gt; &lt;span class=&#34;z-err&#34;&gt;/*&lt;&#x2F;span&gt; &lt;span class=&#34;z-err&#34;&gt;...&lt;&#x2F;span&gt; &lt;span class=&#34;z-err&#34;&gt;*/&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt;&#xA;          &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;name&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;Sort.Field&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;in&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;query&amp;#34;&lt;&#x2F;span&gt; &lt;span class=&#34;z-err&#34;&gt;/*&lt;&#x2F;span&gt; &lt;span class=&#34;z-err&#34;&gt;...&lt;&#x2F;span&gt; &lt;span class=&#34;z-err&#34;&gt;*/&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt;&#xA;          &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;name&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;Sort.Direction&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;in&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;query&amp;#34;&lt;&#x2F;span&gt; &lt;span class=&#34;z-err&#34;&gt;/*&lt;&#x2F;span&gt; &lt;span class=&#34;z-err&#34;&gt;...&lt;&#x2F;span&gt; &lt;span class=&#34;z-err&#34;&gt;*/&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-c1&#34;&gt;// ...&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;This corresponds to a query string formatted in dot notation &lt;code&gt;field.subfield&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;http&#34; data-highlighted=&#34;true&#34; class=&#34;language-http z-code z-chroma&#34;&gt;&lt;code class=&#34;language-http&#34; data-lang=&#34;http&#34;&gt;&lt;span class=&#34;z-err&#34;&gt;GET /products?&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;  Filter.Title.Contains=ssd&amp;amp;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;  Sort.Field=price&amp;amp;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;  Sort.Direction=ASC&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;and it is rendered as a form with separate inputs in Swagger UI.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/posts/aspnetcore-model-binding-json-query-params/./swagger_fromquery.png&#34; alt=&#34;swagger ui with separate inputs&#34; /&gt;&lt;&#x2F;p&gt;&#xA;&lt;p&gt;If we try to send the parameter as JSON we can&#x27;t get it to bind to a complex type. &lt;code&gt;SearchQuery query&lt;&#x2F;code&gt; parameter is always empty.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We could &lt;code&gt;POST&lt;&#x2F;code&gt; this JSON in the request body, but that breaks the convention and doesn&#x27;t signal that the endpoint is actually idempotent.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;binding-json-query-parameters&#34;&gt;Binding JSON query parameters&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;We can work around it by accepting a JSON string, then deserializing it inside the action.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-na&#34;&gt;[HttpGet]&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ActionResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;SearchResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;SearchProducts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;    [FromQuery(Name = &amp;#34;query&amp;#34;)]&lt;&#x2F;span&gt; &lt;span class=&#34;z-kt&#34;&gt;string&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;queryJson&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;query&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;DeserializeJson&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;queryJson&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;This works, but I don&#x27;t like parsing, model binding &amp;amp; validation myself that MVC platform already does for me.&#xA;It&#x27;d be best if we could utilize ASP.NET as much as possible.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;MVC gives us a better extension point: model binders. We will use them to create our own binder and use it just like we would with &lt;code&gt;[FromQuery]&lt;&#x2F;code&gt;, or &lt;code&gt;[FromBody]&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;&#xA;&lt;h3 id=&#34;creating-a-custom-model-binder&#34;&gt;Creating a custom model binder&lt;&#x2F;h3&gt;&#xA;&lt;p&gt;We start with subclassing &lt;code&gt;ModelBinderAttribute&lt;&#x2F;code&gt;. It&#x27;s a concrete class, so we don&#x27;t have to implement anything, but we want to specify that we will be using a custom binder.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;internal&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;FromJsonQueryAttribute&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ModelBinderAttribute&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;FromJsonQueryAttribute&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;BinderType&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;typeof&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;JsonQueryBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We can annotate the action parameter with it directly instead of using &lt;code&gt;[ModelBinder]&lt;&#x2F;code&gt; attribute:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ActionResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;SearchResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;SearchProducts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;    [FromJsonQuery]&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ProductySearchQuery&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;query&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;...&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now let&#x27;s create the &lt;code&gt;JsonQueryBinder&lt;&#x2F;code&gt; class to get it working. It needs to implement &lt;code&gt;IModelBinder&lt;&#x2F;code&gt; interface. The steps we need to follow are:&lt;&#x2F;p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Get raw JSON from the request.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&lt;code&gt;ModelBindingContext&lt;&#x2F;code&gt; contains &lt;code&gt;ValueProvider&lt;&#x2F;code&gt; for retrieving values from the request, if it&#x27;s not enough, you can also use &lt;code&gt;HttpContext&lt;&#x2F;code&gt; and work on the request directly.&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Turn it into a useful type.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We deserialize it with &lt;a href=&#34;https://www.nuget.org/packages/System.Text.Json&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;System.Text.Json&lt;&#x2F;a&gt;, which is available for all .NET versions, but you can&#x27;t go wrong with &lt;a href=&#34;https://www.nuget.org/packages/Newtonsoft.Json/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Json.NET&lt;&#x2F;a&gt;, either.&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Validate it (&lt;a href=&#34;#utilizing-asp-net-core-s-validation-tools&#34;&gt;later&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Accept / reject the result.&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;internal&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;JsonQueryBinder&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IModelBinder&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;private&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;readonly&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ILogger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;JsonQueryBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;_logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;JsonQueryBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ILogger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;JsonQueryBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;_logger&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;BindModelAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelBindingContext&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;value&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ValueProvider&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetValue&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;FieldName&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;).&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;FirstValue&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;value&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;null&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CompletedTask&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-k&#34;&gt;try&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;parsed&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;JsonSerializer&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Deserialize&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-k&#34;&gt;value&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelType&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;JsonSerializerOptions&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;JsonSerializerDefaults&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Web&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Result&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ModelBindingResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Success&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;parsed&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;catch&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Exception&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;_logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;LogError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s&#34;&gt;&amp;#34;Failed to bind &amp;#39;{FieldName}&amp;#39;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;FieldName&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Result&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ModelBindingResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Failed&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CompletedTask&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now when we send a request to the endpoint, it works! &lt;code&gt;SearchQuery query&lt;&#x2F;code&gt; parameter is filled correctly.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-err&#34;&gt;GET&lt;&#x2F;span&gt; &lt;span class=&#34;z-err&#34;&gt;/products?query=&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;filter&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;title&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;contains&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;ssd&amp;#34;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;sort&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;field&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;price&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;direction&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;ASC&amp;#34;&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;But validation doesn&#x27;t work yet. If we omit a &lt;code&gt;[Required]&lt;&#x2F;code&gt; property or pass in a string to boolean, it just fails. We can improve this further.&lt;&#x2F;p&gt;&#xA;&lt;h3 id=&#34;utilizing-asp-net-core-s-validation-tools&#34;&gt;Utilizing ASP.NET Core&#x27;s validation tools&lt;&#x2F;h3&gt;&#xA;&lt;p&gt;After we deserialize the payload, we can let ASP.NET take care of the validation. &lt;a href=&#34;https://github.com/dotnet/aspnetcore/blob/85a6cb07ae2e1adf5a03740f8d40333b7b8e360c/src/Mvc/Mvc.Core/src/ModelBinding/Validation/IObjectModelValidator.cs#L11&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;IObjectModelValidator&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; interface gives us the tools to validate an object graph with all its properties and sub types.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We inject it into our binder, and call its &lt;code&gt;.Validate()&lt;&#x2F;code&gt; method after deserialization.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;internal&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;JsonQueryBinder&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IModelBinder&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-c1&#34;&gt;// inject the validator in constructor&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;private&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;readonly&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IObjectModelValidator&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;_validator&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;BindModelAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelBindingContext&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;try&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;parsed&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;JsonSerializer&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Deserialize&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;value&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelType&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;JsonSerializerOptions&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;JsonSerializerDefaults&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Web&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;));&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Result&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ModelBindingResult&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Success&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;parsed&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&#xA;            &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;parsed&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;!=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;null&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;_validator&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Validate&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;                    &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ActionContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                    &lt;span class=&#34;z-n&#34;&gt;validationState&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ValidationState&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                    &lt;span class=&#34;z-n&#34;&gt;prefix&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-kt&#34;&gt;string&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Empty&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                    &lt;span class=&#34;z-n&#34;&gt;model&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;parsed&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;catch&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;JsonException&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;_logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;LogError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s&#34;&gt;&amp;#34;Failed to bind parameter &amp;#39;{FieldName}&amp;#39;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;FieldName&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ActionContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelState&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;TryAddModelError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;key&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;exception&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelMetadata&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;catch&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Exception&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;when&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;e&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;is&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;FormatException&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;||&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;e&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;is&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;OverflowException&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;_logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;LogError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s&#34;&gt;&amp;#34;Failed to bind parameter &amp;#39;{FieldName}&amp;#39;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;FieldName&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ActionContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelState&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;TryAddModelError&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kt&#34;&gt;string&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Empty&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bindingContext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ModelMetadata&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CompletedTask&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now if we have this annotated model:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;record&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;SearchSort&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;    [Required]&lt;&#x2F;span&gt; &lt;span class=&#34;z-kt&#34;&gt;string&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Field&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;    [RegularExpression(&amp;#34;ASC|DESC&amp;#34;)]&lt;&#x2F;span&gt; &lt;span class=&#34;z-kt&#34;&gt;string&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Direction&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;sending an invalid JSON returns a nicely formatted HTTP 400 error&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;type&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;https://tools.ietf.org/html/rfc7231#section-6.5.1&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;title&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;One or more validation errors occurred.&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;status&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;400&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;traceId&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;00-15833e541708ac49b4829f02c40008d0-4830117fc8dbca43-00&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;errors&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;Sort.Field&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;The Field field is required.&amp;#34;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;Sort.Direction&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;The field Direction must match the regular expression &amp;#39;ASC|DESC&amp;#39;.&amp;#34;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;q.Sort.Field&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;The Field field is required.&amp;#34;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;q.Sort.Direction&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;The field Direction must match the regular expression &amp;#39;ASC|DESC&amp;#39;.&amp;#34;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;adding-openapi-support&#34;&gt;Adding OpenAPI support&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;When we created the &lt;code&gt;FromJsonQueryAttribute&lt;&#x2F;code&gt;, its &lt;code&gt;BindingSource&lt;&#x2F;code&gt; property was set to &lt;a href=&#34;https://github.com/dotnet/aspnetcore/blob/85a6cb07ae2e1adf5a03740f8d40333b7b8e360c/src/Mvc/Mvc.Core/src/BindPropertyAttribute.cs#L65&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;BindingSource.Custom&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;. This keeps Swagger from displaying the properties as individual inputs.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/posts/aspnetcore-model-binding-json-query-params/./swagger_fromjsonquery.png&#34; alt=&#34;swagger ui with single input&#34; /&gt;&lt;&#x2F;p&gt;&#xA;&lt;p&gt;But still, if we try to send a request using the Swagger UI, it serializes the parameter in bracket notation. To solve this problem, we can provide a custom API description for endpoints with &lt;code&gt;[FromJsonQuery]&lt;&#x2F;code&gt; parameters to tell clients to actually serialize the parameter as JSON.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;OpenAPI specification &lt;a href=&#34;https://swagger.io/docs/specification/describing-parameters/#schema-vs-content&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;addresses&lt;&#x2F;a&gt; our exact use-case, and gives us the option to specify a parameter schema with a specific content type like so:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;paths&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;/api/products&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;get&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;parameters&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;          &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;name&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;query&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;in&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;query&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;content&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;              &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;application/json&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;schema&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                  &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;$ref&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;#/components/schemas/ProductSearchQuery&amp;#34;&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;              &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-c1&#34;&gt;// ...&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We can fix it by hooking into SwashBuckle with an &lt;a href=&#34;https://github.com/domaindrivendev/Swashbuckle.AspNetCore#operation-filters&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;operation filter&lt;&#x2F;a&gt; and modify the generated schema. From there, we correct the parameters annotated with &lt;code&gt;[FromJsonQuery]&lt;&#x2F;code&gt; with the right schema.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;internal&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;static&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;JsonQuerySwaggerGenExtensions&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;static&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;SwaggerGenOptions&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;AddJsonQuerySupport&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;this&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;SwaggerGenOptions&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;OperationFilter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;JsonQueryOperationFilter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;();&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;private&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;JsonQueryOperationFilter&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IOperationFilter&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;void&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Apply&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;OpenApiOperation&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;operation&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;OperationFilterContext&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;jsonQueryParams&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ApiDescription&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ActionDescriptor&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Parameters&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Where&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ad&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ad&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BindingInfo&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BinderType&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;typeof&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;JsonQueryBinder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Select&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ad&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ad&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Name&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ToList&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;&#xA;            &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(!&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;jsonQueryParams&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Any&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;())&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;            &lt;span class=&#34;z-k&#34;&gt;foreach&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;p&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;operation&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Parameters&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Where&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;p&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;jsonQueryParams&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Contains&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;p&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Name&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)))&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-c1&#34;&gt;// move the schema under application/json content type&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;p&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Content&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Dictionary&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-kt&#34;&gt;string&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;OpenApiMediaType&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;()&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-na&#34;&gt;                    [MediaTypeNames.Application.Json]&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;OpenApiMediaType&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;                    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                        &lt;span class=&#34;z-n&#34;&gt;Schema&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;p&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Schema&lt;&#x2F;span&gt;&#xA;                    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-p&#34;&gt;};&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-c1&#34;&gt;// then clear it&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;p&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Schema&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;null&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;When writing a plugin for a third party library, I prefer having an extension method to hide the glue code, and keep the implementations as nested private classes.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Anyway, now we can enable the filter in our &lt;code&gt;Startup&lt;&#x2F;code&gt; class.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;void&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ConfigureServices&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IServiceCollection&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddSwaggerGen&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;c&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;c&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddJsonQuerySupport&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;});&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;When we send a request, the query parameter is now serialized as JSON 💃.&#xA;Our API is now easier to use, well-annotated and shows up with the correct Swagger UI for testing.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Cheers ✌.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;references&#34;&gt;References&lt;&#x2F;h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-5.0&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Model Binding in ASP.NET Core&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-5.0#custom-model-binder-sample&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Custom model binder&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/domaindrivendev/Swashbuckle.AspNetCore&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;SwashBuckle&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr /&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;You need to annotate your controller with &lt;code&gt;[ApiController]&lt;&#x2F;code&gt; attribute to get auto-validation, along with other useful behaviors for APIs. See &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-5.0#apicontroller-attribute&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;docs&lt;&#x2F;a&gt; for further info.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ol&gt;&#xA;&lt;&#x2F;div&gt;&#xA;</description>
        </item>
        <item>
            <title>Checking if an executable exists in PATH using Node.js</title>
            <pubDate>Tue, 26 Jan 2021 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/checking-executable-exists-in-path-using-node/</link>
            <guid>https://abdus.dev/posts/checking-executable-exists-in-path-using-node/</guid>
            <description>&lt;p&gt;I was looking for a way to distribute a Powershell script.&#xA;I decided to wrap it inside an npm package and let users &lt;a href=&#34;/posts/running-powershell-script-in-node/&#34;&gt;run the Powershell script&lt;&#x2F;a&gt; using:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;npx&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;package-name&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[...&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;which passes arguments to Powershell and let it run the actual script, which is bundled with the npm package.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;problem&#34;&gt;Problem&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;The problem arised when I wanted to check if a modern version of Powershell exists on the user&#x27;s machine.&#xA;The first solution that came to my mind was checking executable version&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;pwsh&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-command&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;echo &lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$PSVersionTable&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;.PSVersion.ToString()&amp;#34;&lt;&#x2F;span&gt; &lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;but it runs slowly. It also doesn&#x27;t handle commands without &lt;code&gt;--version&lt;&#x2F;code&gt; or any command switch at all. So trying to run the program to see if actually exists is not a great way to go about it.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;solution&#34;&gt;Solution&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;A faster solution is to check if &lt;code&gt;pwsh.exe&lt;&#x2F;code&gt; exists in &lt;code&gt;PATH&lt;&#x2F;code&gt;. There&#x27;s a pre-made solution for this, as with anything, called &lt;a href=&#34;https://github.com/springernature/hasbin&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;hasbin&lt;&#x2F;a&gt; but it&#x27;s quite old and uses external dependencies for async operations that are native to modern JS.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&lt;code&gt;findExecutable&lt;&#x2F;code&gt; function builds a list of candidates paths from the combinations of all directories in &lt;code&gt;PATH&lt;&#x2F;code&gt; and all extensions (no-op if not on Windows) and checks if any of them exists and returns the first result.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;If I were to look for &lt;code&gt;pwsh.exe&lt;&#x2F;code&gt; and &lt;code&gt;PATH&lt;&#x2F;code&gt; were &lt;code&gt;a/b;c/d&lt;&#x2F;code&gt;, it would try &lt;code&gt;a/b/pwsh.exe&lt;&#x2F;code&gt;, &lt;code&gt;c/d/pwsh.exe&lt;&#x2F;code&gt; and so on.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;js&#34; data-highlighted=&#34;true&#34; class=&#34;language-js z-code z-chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;path&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;path&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;fs&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;fs/promises&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-cm&#34;&gt;/**&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; * @param {string} exe executable name (without extension if on Windows)&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; * @return {Promise&amp;lt;string|null&amp;gt;} executable path if found&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; * */&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kr&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;function&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;findExecutable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;exe&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;envPath&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;process&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;env&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;PATH&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;||&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;envExt&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;process&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;env&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;PATHEXT&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;||&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;pathDirs&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;envPath&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;replace&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-sr&#34;&gt;/[&amp;#34;]+/g&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;split&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;delimiter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;filter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Boolean&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;extensions&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;envExt&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;split&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;candidates&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;pathDirs&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;flatMap&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;((&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;d&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nx&#34;&gt;extensions&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;map&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;((&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;ext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;join&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;d&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;exe&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;+&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;ext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;try&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-kr&#34;&gt;await&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;Promise&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;any&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;candidates&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;map&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;checkFileExists&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;));&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;catch&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;null&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kr&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;function&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;checkFileExists&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;filePath&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;((&lt;&#x2F;span&gt;&lt;span class=&#34;z-kr&#34;&gt;await&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;fs&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;stat&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;filePath&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)).&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;isFile&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;())&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;filePath&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;throw&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;Error&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;Not a file&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We can finally use &lt;code&gt;findExecutable&lt;&#x2F;code&gt; to get the most recent Powershell installation.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;js&#34; data-highlighted=&#34;true&#34; class=&#34;language-js z-code z-chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;z-kr&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;function&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;getPowershellVariant&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kr&#34;&gt;await&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;findExecutable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;pwsh&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;pwsh&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kr&#34;&gt;await&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;findExecutable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;powershell&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;powershell&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;throw&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;Error&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;Powershell is not installed, or not in PATH&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;what-is-pathext-for&#34;&gt;What is &lt;code&gt;PATHEXT&lt;&#x2F;code&gt; for?&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Windows uses &lt;code&gt;PATHEXT&lt;&#x2F;code&gt; environment variable to determine the file name for an executable if you don&#x27;t specify an extension. On my PC, &lt;code&gt;PATHEXT&lt;&#x2F;code&gt; content is:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;PS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$env:PATHEXT&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;COM&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;EXE&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BAT&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CMD&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;VBS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;VBE&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;JS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;JSE&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;WSF&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;WSH&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;MSC&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PY&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PYW&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ps1&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;CPL&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;That means, if I just type &lt;code&gt;pwsh&lt;&#x2F;code&gt;, it looks for &lt;code&gt;pwsh.com&lt;&#x2F;code&gt;, &lt;code&gt;pwsh.exe&lt;&#x2F;code&gt;, &lt;code&gt;pwsh.bat&lt;&#x2F;code&gt; and so on, and executes the first one it finds.&#xA;That also means if an extension is not on the list, I have to type in the extension.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;This is not an issue on UNIX systems, because they use an &lt;a href=&#34;https://en.wikipedia.org/wiki/File-system_permissions#Symbolic_notation&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;executable bit &lt;code&gt;+x&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; to specify if a file can be executed, regardless of whether it has an extension or not.&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr /&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;This usually involves running a binary with &lt;code&gt;--version&lt;&#x2F;code&gt; option, but Powershell behaves completely differently and uses &lt;code&gt;-version&lt;&#x2F;code&gt; option to specify the version of Powershell to run the command/script against.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ol&gt;&#xA;&lt;&#x2F;div&gt;&#xA;</description>
        </item>
        <item>
            <title>Running a Powershell script in Node</title>
            <pubDate>Thu, 21 Jan 2021 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/running-powershell-script-in-node/</link>
            <guid>https://abdus.dev/posts/running-powershell-script-in-node/</guid>
            <description>&lt;p&gt;I needed a way to call a Powershell script from Node.&#xA;Node provides &lt;code&gt;child_process&lt;&#x2F;code&gt; module, which provides tools to execute and communicate with external processes.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We&#x27;re going to use &lt;code&gt;spawn&lt;&#x2F;code&gt; for this. It allows us to pipe the &lt;code&gt;stdout&lt;&#x2F;code&gt;/&lt;code&gt;stderr&lt;&#x2F;code&gt; of a process back to caller.&#xA;This helps the caller to see what the process is actually doing behind the scenes.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;js&#34; data-highlighted=&#34;true&#34; class=&#34;language-js z-code z-chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;spawn&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;require&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;child_process&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-cm&#34;&gt;/**&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; * @param {string} executable&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; * @param {string[]} args&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; * @param {import(&amp;#39;child_process&amp;#39;).SpawnOptions} opts&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; * @return {Promise&amp;lt;number&amp;gt;} return code&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; * */&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kr&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;function&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;run&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;executable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;opts&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{})&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;Promise&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;((&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;resolve&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;reject&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;child&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;spawn&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;executable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-nx&#34;&gt;shell&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;true&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-nx&#34;&gt;stdio&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;pipe&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;process&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;process&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;stderr&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;...&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;opts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;});&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nx&#34;&gt;child&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;on&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;error&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;reject&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nx&#34;&gt;child&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;on&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;exit&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;code&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;code&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;===&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-nx&#34;&gt;resolve&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;code&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;else&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;e&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;Error&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Process exited with error code &amp;#39;&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;+&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;code&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-nx&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;code&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;code&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-nx&#34;&gt;reject&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;});&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;});&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;I like writing my own wrapper to promisify &lt;code&gt;spawn&lt;&#x2F;code&gt;. It helps me return the exit code to the caller and I also get a better intellisense while writing scripts.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We can call &lt;code&gt;run&lt;&#x2F;code&gt; inside a try-catch block to capture errors and set a correct exit code.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;js&#34; data-highlighted=&#34;true&#34; class=&#34;language-js z-code z-chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;z-k&#34;&gt;try&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;code&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kr&#34;&gt;await&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;run&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;powershell&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;-executionpolicy&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;unrestricted&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;-file&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;script.ps1&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nx&#34;&gt;process&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;exit&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;code&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;catch&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nx&#34;&gt;console&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;error&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nx&#34;&gt;process&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;exit&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;code&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;||&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;1&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;</description>
        </item>
        <item>
            <title>Monitoring clipboard contents on Windows with Python</title>
            <pubDate>Mon, 30 Nov 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/monitor-clipboard/</link>
            <guid>https://abdus.dev/posts/monitor-clipboard/</guid>
            <description>&lt;p&gt;I was looking for ways to listen to the clipboard and get called for updates as its content changes.&#xA;I found a couple of ways to achieve this in Python. Some solutions poll for changes, others use ctypes and Win32 APIs.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Working with C bindings in Python is frustrating. The debugger doesn&#x27;t work well with pointers and C types. You have to build your own C structs to unpack a pointer. Win32 APIs are no exception, as they&#x27;re written in C/C++ and Python is probably not the best language to use them. But still, it works well enough for our purposes.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We&#x27;ll explore how do to it in Python using Win32 APIs, or alternatively integrating &lt;a href=&#34;#using-dumpclip-polling-for-changes&#34;&gt;a utility&lt;&#x2F;a&gt; I&#x27;ve written in C# that retrieves clipboard contents or streams updates to it. I had written this earlier when I didn&#x27;t know much about Win32 APIs or how to use them, but it still functions well, so I&#x27;m leaving it here in this post as reference.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;To make working with Win32 APIs easier, we need to install &lt;a href=&#34;https://github.com/mhammond/pywin32&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;pywin32&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; package which provides most of the primitives and types for Win32 APIs, though it&#x27;s not a strict dependency.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;monitoring-clipboard-updates&#34;&gt;Monitoring clipboard updates&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Windows provides a couple of methods for data exchange between applications. Clipboard is one of them. All applications have access to it. But we first need to create a primitive &amp;quot;application&amp;quot; that Windows recognizes. We subscribe it for the clipboard updates.&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;has-recommend&#34;&gt;&#xA;&lt;p&gt;Windows uses &lt;strong&gt;window&lt;&#x2F;strong&gt;s (hah!) as the building block of applications. I&#x27;ve written about how windows and messaging works on Windows in &lt;a href=&#34;/posts/python-monitor-usb/#listening-to-windows-wm-devicechange-messages&#34;&gt;another post&lt;&#x2F;a&gt; where I explored USB hotplugging events, which might be worth reading.&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;recommend&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;/posts/python-monitor-usb/#listening-to-windows-wm-devicechange-messages&#34;&gt;Detecting USB drive insertion &amp;amp; removal on Windows using Python&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;div&gt;&#xA;&lt;&#x2F;div&gt;&#xA;&lt;p&gt;Let&#x27;s create a window, and set &lt;code&gt;print&lt;&#x2F;code&gt; function as its &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/winmsg/using-window-procedures&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;window procedure&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32api&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32gui&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;create_window&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    Create a window for listening to messages&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    :return: window hwnd&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;WNDCLASS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;lpfnWndProc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;lpszClassName&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;demo&amp;#39;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hInstance&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32api&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetModuleHandle&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;class_atom&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;RegisterClass&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CreateWindow&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;class_atom&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;demo&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hInstance&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;create_window&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PumpMessages&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;When we run it doesn&#x27;t do much except to dump messages sent by Windows to console. We receive the first message &lt;code&gt;WM_DWMNCRENDERINGCHANGED&lt;&#x2F;code&gt;, which doesn&#x27;t concern us.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We need to register this window as a &amp;quot;clipboard format listener&amp;quot; using &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/dataxchg/using-the-clipboard#creating-a-clipboard-format-listener&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;AddClipboardFormatListener&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; API, to get notified by Windows whenever the contents of the clipboard change.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;ctypes&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-c1&#34;&gt;# ...&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;create_window&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;ctypes&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;windll&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;user32&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddClipboardFormatListener&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PumpMessages&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now when we run this, it still prints the previous message, but when you copy something to the clipboard it receives another message:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;console&#34; data-highlighted=&#34;true&#34; class=&#34;language-console z-code z-chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;z-go&#34;&gt;2033456 799 1 0&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;2033456 797 8 0&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Decoding the second message:&lt;&#x2F;p&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;Value&lt;&#x2F;th&gt;&#xA;&lt;th&gt;Hex&lt;&#x2F;th&gt;&#xA;&lt;th&gt;Message&lt;&#x2F;th&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;&#x2F;thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;797&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;0x031D&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;WM_CLIPBOARDUPDATE&lt;&#x2F;code&gt; 🥳&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;&#x2F;tbody&gt;&#xA;&lt;&#x2F;table&gt;&#xA;&lt;p&gt;We&#x27;ve received a &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/dataxchg/wm-clipboardupdate&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;WM_CLIPBOARDUPDATE&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; message notifying us that the clipboard content has changed. Now we can build our script around it.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;threading&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;ctypes&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32api&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32gui&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;Clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;_create_window&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        Create a window for listening to messages&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        :return: window hwnd&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        &amp;#34;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;WNDCLASS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;lpfnWndProc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_process_message&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;lpszClassName&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__class__&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hInstance&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32api&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetModuleHandle&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;class_atom&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;RegisterClass&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CreateWindow&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;class_atom&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__class__&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hInstance&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;_process_message&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;msg&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wparam&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;lparam&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;WM_CLIPBOARDUPDATE&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-mh&#34;&gt;0x031D&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;msg&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;WM_CLIPBOARDUPDATE&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;clipboard updated!&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;listen&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;runner&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_create_window&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;ctypes&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;windll&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;user32&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddClipboardFormatListener&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PumpMessages&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;threading&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Thread&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;target&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;runner&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;daemon&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;start&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;while&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;is_alive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;join&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;0.25&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;clipboard&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;listen&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;One thing we need to watch out for is that because &lt;code&gt;win32gui.PumpMessages()&lt;&#x2F;code&gt; is blocking, we cannot stop the script using &lt;kbd&gt;Ctrl&lt;&#x2F;kbd&gt; + &lt;kbd&gt;C&lt;&#x2F;kbd&gt;. So we run it inside a thread, which lets &lt;code&gt;KeyboardInterrupt&lt;&#x2F;code&gt; to bubble up and terminate the script.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;When we run it, and copy something (text, files) and check the console, we can see it prints &lt;code&gt;clipboard updated!&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Now that we have the notification working, let&#x27;s retrieve the what&#x27;s actually in the clipboard.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;getting-clipboard-contents&#34;&gt;Getting clipboard contents&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/dataxchg/using-the-clipboard#creating-a-clipboard-format-listener&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Windows clipboard&lt;&#x2F;a&gt; has a concept called &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats#constants&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&amp;quot;clipboard format&amp;quot;&lt;&#x2F;a&gt;. When you copy something, (depending on application) the payload is also attached a bunch of metadata, allowing it to be used in various contexts. For example, when you copy a table from a webpage, you have the option to paste it as plain text, or paste it in Excel and have it formatted as a table. You can copy files, images, screenshots into the clipboard and each payload gets stored formatted (again, depending on how the application sets the clipboard content).&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Therefore, if we want to get the clipboard contents, we need to specify which format we want in. For now, we&#x27;ll be dealing with:&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;table&#34;&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;Format&lt;&#x2F;th&gt;&#xA;&lt;th&gt;Value&lt;&#x2F;th&gt;&#xA;&lt;th&gt;Description&lt;&#x2F;th&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;&#x2F;thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;CF_UNICODETEXT&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;13&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;Unicode text format&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;CF_TEXT&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;1&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;Text format for ANSI text&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;CF_HDROP&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;15&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;List of files&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;CF_BITMAP&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;2&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;Images e.g. screenshots&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;&#x2F;tbody&gt;&#xA;&lt;&#x2F;table&gt;&#xA;&lt;&#x2F;div&gt;&#xA;&lt;p&gt;To read the clipboard, we&#x27;ll use &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/desktop/api/Winuser/nf-winuser-openclipboard&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;OpenClipboard&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; to set a lock first. This ensures other programs can&#x27;t modify the clipboard while we&#x27;re trying to read it. We need to release the lock with &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/desktop/api/Winuser/nf-winuser-closeclipboard&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;CloseClipboard&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; once we&#x27;re done.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Then we&#x27;ll call &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/desktop/api/Winuser/nf-winuser-isclipboardformatavailable&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;IsClipboardFormatAvailable&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; to query a format, then get its contents using &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/desktop/api/Winuser/nf-winuser-getclipboarddata&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;GetClipboardData&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, or fallback to other formats.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;pathlib&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;dataclasses&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;dataclass&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;typing&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Union&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Optional&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32con&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-nd&#34;&gt;@dataclass&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;Clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nb&#34;&gt;type&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;value&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Union&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]]&lt;&#x2F;span&gt;&#xA;    &#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;read_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Optional&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;try&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;OpenClipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IsClipboardFormatAvailable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CF_HDROP&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;data&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;tuple&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetClipboardData&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CF_HDROP&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;files&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;f&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;data&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;])&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;elif&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IsClipboardFormatAvailable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CF_UNICODETEXT&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;data&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetClipboardData&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CF_UNICODETEXT&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;text&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;data&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;elif&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IsClipboardFormatAvailable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CF_TEXT&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;data&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;bytes&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetClipboardData&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CF_TEXT&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;text&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;data&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;decode&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;())&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;elif&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IsClipboardFormatAvailable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CF_BITMAP&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-c1&#34;&gt;# TODO: handle screenshots&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;pass&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;finally&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CloseClipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;read_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;())&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;When we run it, and try copying some text or files, it prints the contents to the console:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;console&#34; data-highlighted=&#34;true&#34; class=&#34;language-console z-code z-chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;z-go&#34;&gt;Clip(type=&amp;#39;text&amp;#39;, value=&amp;#39;read_clipboard&amp;#39;)&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;Clip(type=&amp;#39;files&amp;#39;, value=[WindowsPath(&amp;#39;C:/Python39/vcruntime140_1.dll&amp;#39;), WindowsPath(&amp;#39;C:/Python39/python.exe&amp;#39;)])&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now let&#x27;s bring it all together:&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;clipboard-listener-in-python&#34;&gt;Clipboard listener in Python&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;I&#x27;ve placed &lt;code&gt;read_clipboard&lt;&#x2F;code&gt; inside &lt;code&gt;Clipboard&lt;&#x2F;code&gt; class, which creates a window and subscribes to clipboard updates. When the clipboard content changes, it triggers suitable callbacks with the parsed content.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;For convenience, you can enable &lt;code&gt;trigger_at_start&lt;&#x2F;code&gt; to trigger callbacks with the current clipboard content immediately after listening.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;ctypes&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;threading&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;dataclasses&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;dataclass&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;pathlib&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;typing&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Union&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Optional&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32api&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32gui&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;Clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nd&#34;&gt;@dataclass&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;Clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nb&#34;&gt;type&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;value&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Union&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]]&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-fm&#34;&gt;__init__&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;trigger_at_start&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;bool&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;False&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;on_text&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[[&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;on_update&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;on_files&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[[&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_trigger_at_start&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;trigger_at_start&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_on_update&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;on_update&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_on_files&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;on_files&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_on_text&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;on_text&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;_create_window&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        Create a window for listening to messages&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        :return: window hwnd&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        &amp;#34;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;WNDCLASS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;lpfnWndProc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_process_message&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;lpszClassName&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__class__&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hInstance&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32api&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetModuleHandle&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;class_atom&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;RegisterClass&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CreateWindow&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;class_atom&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__class__&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hInstance&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;_process_message&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;msg&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wparam&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;lparam&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;WM_CLIPBOARDUPDATE&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-mh&#34;&gt;0x031D&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;msg&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;WM_CLIPBOARDUPDATE&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_process_clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;_process_clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;clip&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;read_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;not&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_on_update&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_on_update&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;type&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;text&amp;#39;&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;and&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_on_text&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_on_text&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;value&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;elif&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;type&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;files&amp;#39;&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;and&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_on_text&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_on_files&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;value&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;z-nd&#34;&gt;@staticmethod&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;read_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Optional&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;try&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;OpenClipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;            &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;get_formatted&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;fmt&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IsClipboardFormatAvailable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;fmt&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;                    &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetClipboardData&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;fmt&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&#xA;&#xA;            &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;files&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;get_formatted&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CF_HDROP&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;files&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;f&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;files&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;])&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;elif&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;text&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;get_formatted&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CF_UNICODETEXT&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;text&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;text&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;elif&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;text_bytes&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;get_formatted&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CF_TEXT&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;text&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;text_bytes&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;decode&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;())&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;elif&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bitmap_handle&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;get_formatted&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CF_BITMAP&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-c1&#34;&gt;# TODO: handle screenshots&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-k&#34;&gt;pass&lt;&#x2F;span&gt;&#xA;&#xA;            &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;finally&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;win32clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CloseClipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;listen&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_trigger_at_start&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_process_clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;runner&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_create_window&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;ctypes&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;windll&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;user32&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddClipboardFormatListener&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PumpMessages&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;threading&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Thread&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;target&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;runner&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;daemon&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;start&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;while&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;is_alive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;join&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;0.25&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;clipboard&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_update&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;trigger_at_start&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;listen&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;When we run it and copy some text, or some files, it dumps the clipboard content just as we want it.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;console&#34; data-highlighted=&#34;true&#34; class=&#34;language-console z-code z-chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;z-go&#34;&gt;Clipboard.Clip(type=&amp;#39;text&amp;#39;, value=&amp;#39;Clipboard&amp;#39;)&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;Clipboard.Clip(type=&amp;#39;files&amp;#39;, value=[WindowsPath(&amp;#39;C:/Python39/python.exe&amp;#39;)])&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;I haven&#x27;t managed to retrieve bitmap from the clipboard when taking a screenshot yet, though it shouldn&#x27;t be too difficult.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;It should prove useful for the use case where when you take a screenshot, you can save it automatically as PNG, upload it and copy its URL to clipboard, ready for pasting.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;using-dumpclip-polling-for-changes&#34;&gt;Using &lt;strong&gt;dumpclip&lt;&#x2F;strong&gt;: polling for changes&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Before I could navigate around Win32 APIs easily, I used higher level APIs provided in C# to listen to the clipboard. On that end, I created a mini utility called &lt;a href=&#34;https://abdus.dev/projects/dumpclip/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;strong&gt;dumpclip&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; that prints the clipboard content to console as JSON or streams clipboard updates.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;The first version of &lt;strong&gt;dumpclip&lt;&#x2F;strong&gt; had a single function: dumping the clipboard content to console as JSON.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;dumpclip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;v1&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;exe&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;text&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;monitor&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Calling it from Python is quite straightforward using &lt;code&gt;subprocess&lt;&#x2F;code&gt; module. But that also meant polling for changes every second.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;json&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;subprocess&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;threading&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;time&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;sleep&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;typing&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;get_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;dict&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;run&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;dumpclip.v1.exe&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PIPE&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;text&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;returncode&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;!=&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{}&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;json&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;loads&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;monitor_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[[&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;dict&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;])&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;monitor&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;old&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;while&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;get_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;old&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;!=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;new&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;new&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;old&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;new&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;sleep&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-mi&#34;&gt;1&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;threading&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Thread&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;target&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;monitor&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;start&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;join&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;__main__&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;monitor_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;It&#x27;s functional, but we can do better.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;using-dumpclip-streaming-clipboard-updates&#34;&gt;Using &lt;strong&gt;dumpclip&lt;&#x2F;strong&gt;: streaming clipboard updates&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;The second iteration of &lt;strong&gt;dumpclip&lt;&#x2F;strong&gt; involved using Win32 APIs. I&#x27;ve used &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/dataxchg/using-the-clipboard#creating-a-clipboard-format-listener&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;AddClipboardFormatListener&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; to register a callback for clipboard changes in C#, then retrieved &amp;amp; dumped its content as the new content came in.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;dumpclip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;v2&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;exe&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;-&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;-listen&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;text&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;ClipboardChanged&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;text&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;monitor&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;files&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;D:\\path\\to\\file.ext&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;This worked much better. I can process its &lt;code&gt;stdout&lt;&#x2F;code&gt; stream, and trigger a callback directly, instead of polling for changes. But &lt;strong&gt;dumpclip&lt;&#x2F;strong&gt; launched in listener mode never terminates. We need to read its stdout in real-time.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;To stream &lt;code&gt;stdout&lt;&#x2F;code&gt; of a process, we need to launch it with &lt;code&gt;subprocess.Popen&lt;&#x2F;code&gt; and pipe its output to &lt;code&gt;subprocess.PIPE&lt;&#x2F;code&gt;.&#xA;Then we can read its &lt;code&gt;stdout&lt;&#x2F;code&gt; in a separate thread. Because, the main thread that launches the process will be waiting for the process to terminate (although it never will).&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;json&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;subprocess&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;threading&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;typing&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;monitor_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[[&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;dict&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;])&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;read_stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Popen&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;line&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;iter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;readline&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;line&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;strip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;payload&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;json&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;loads&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;line&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;payload&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Popen&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;dumpclip.v2.exe&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;--listen&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;text&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PIPE&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;threading&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Thread&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;target&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;read_stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,),&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;start&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;try&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;wait&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;except&lt;&#x2F;span&gt; &lt;span class=&#34;z-ne&#34;&gt;KeyboardInterrupt&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;kill&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;raise&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;__main__&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;monitor_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Because the process doesn&#x27;t terminate, the thread that consumes its output doesn&#x27;t stop, either.&#xA;It keeps processing the output as new content comes in, and idles if there&#x27;s nothing to consume, as &lt;code&gt;proc.stdout.readline()&lt;&#x2F;code&gt; call is blocking.&#xA;When the process gets killed, &lt;code&gt;proc.stdout&lt;&#x2F;code&gt; stops blocking and the thread terminates.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;To prevent blocking interrupt signal and to allow the script and the process terminate, we need to &lt;code&gt;.wait()&lt;&#x2F;code&gt; the subprocess. This allows &lt;code&gt;KeyboardInterrupt&lt;&#x2F;code&gt; to bubble up and terminate the script (and its subprocesses) when we hit &lt;kbd&gt;Ctrl&lt;&#x2F;kbd&gt; + &lt;kbd&gt;C&lt;&#x2F;kbd&gt;.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;using-dumpclip-async-workflow&#34;&gt;Using dumpclip: async workflow&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Just for kicks, I wanted to implement the same operation in async. It turned out to be more straightforward to write and consume. One caveat is that you have to create a wrapper async function to use &lt;code&gt;async&lt;&#x2F;code&gt;/&lt;code&gt;await&lt;&#x2F;code&gt; keywords, so I had to add a &lt;code&gt;main&lt;&#x2F;code&gt; function to do that.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;asyncio&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;json&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;pathlib&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;typing&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;AsyncIterable&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;monitor_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;AsyncIterable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;dict&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;await&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;asyncio&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;create_subprocess_exec&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;dumpclip.exe&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;--listen&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;cwd&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__file__&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;parent&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;resolve&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;asyncio&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PIPE&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;while&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;raw_bytes&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;:=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;await&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;readline&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;text&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;raw_bytes&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;decode&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;strip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;text&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-k&#34;&gt;yield&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;json&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;loads&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;text&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;__main__&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;main&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;clip&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;monitor_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;clip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-n&#34;&gt;asyncio&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;get_event_loop&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;run_until_complete&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;main&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;())&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;That&#x27;s it.&lt;br /&gt;&#xA;Cheers ✌&lt;&#x2F;p&gt;&#xA;&lt;p&gt;If you&#x27;ve found this post useful, consider sharing it.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>dumpclip - a utility for dumping clipboard contents</title>
            <pubDate>Sat, 28 Nov 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/dumpclip/</link>
            <guid>https://abdus.dev/projects/dumpclip/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;/projects/dumpclip/./dumpclip.png&#34; alt=&#34;&#34; /&gt;&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&lt;strong&gt;dumpclip&lt;&#x2F;strong&gt; is a simple utility that prints clipboard contents to the console as JSON. It supports text and files content. You can find the &lt;a href=&#34;https://github.com/abdusco/dumpclip/releases&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;latest release&lt;&#x2F;a&gt; on Github:&lt;&#x2F;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;a class=&#34;repo-card&#34; href=&#34;https://github.com/abdusco/dumpclip&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&#xA;    &lt;span class=&#34;repo-card__name&#34;&gt;&#xA;        &lt;span class=&#34;repo-card__owner&#34;&gt;abdusco&lt;&#x2F;span&gt;&#xA;        &lt;span aria-hidden=&#34;true&#34;&gt;/&lt;&#x2F;span&gt;&#xA;        &lt;strong&gt;dumpclip&lt;&#x2F;strong&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__description&#34;&gt;A utility for dumping clipboard contents to console&lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__meta&#34;&gt;&#xA;        &#xA;        &lt;span&gt;C#&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span aria-label=&#34;3 stars&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-star&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            3&#xA;        &lt;&#x2F;span&gt;&#xA;        &lt;span aria-label=&#34;3 forks&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-fork&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            3&#xA;        &lt;&#x2F;span&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;&lt;&#x2F;a&gt;&#xA;&#xA;&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Copy some text into the clipboard and run the program.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;dumpclip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;exe&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Clipboard contents will be serialized as JSON and written to the console:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;&amp;#34;text&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;monitor&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Copy some files and try again.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;&amp;#34;files&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;D:\\_dev\\web\\abdus.dev\\src\\projects\\askme\\askme.gif&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;D:\\_dev\\web\\abdus.dev\\src\\projects\\askme\\askme.md&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;listen-to-clipboard-changes&#34;&gt;Listen to clipboard changes&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Run the program with &lt;code&gt;--listen&lt;&#x2F;code&gt; flag.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;dumpclip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;exe&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;-&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;-listen&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;The program will listen to the clipboard and dump contents to the console when the clipboard content changes. You can stop listening by exiting the program with &lt;kbd&gt;Ctrl&lt;&#x2F;kbd&gt; + &lt;kbd&gt;C&lt;&#x2F;kbd&gt;&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;&amp;#34;files&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;D:\\_dev\\windows\\ClipboardDemo\\DumpClipboard\\bin\\Release\\dumpclip.exe&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;&amp;#34;text&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;System.ValueTuple&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h3 id=&#34;python-script-for-monitoring-clipboard&#34;&gt;Python script for monitoring clipboard&lt;&#x2F;h3&gt;&#xA;&lt;p&gt;The main reason I&#x27;ve built this utility was to integrate it into a Python script.&#xA;So here&#x27;s a simple function that runs the app and captures its stdout and calls the given callback.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;json&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;subprocess&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;threading&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;typing&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;monitor_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[[&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;dict&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;])&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;listen_stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Popen&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;line&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;iter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;readline&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;line&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;strip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;payload&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;json&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;loads&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;line&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;payload&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Popen&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;dumpclip.exe&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;--listen&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;text&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;universal_newlines&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PIPE&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;threading&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Thread&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;target&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;listen_stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,),&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;th&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;start&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;try&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;wait&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;except&lt;&#x2F;span&gt; &lt;span class=&#34;z-ne&#34;&gt;KeyboardInterrupt&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;kill&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;raise&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;__main__&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;monitor_clipboard&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;I&#x27;ve also written &lt;a href=&#34;/posts/monitor-clipboard/&#34;&gt;a short post&lt;&#x2F;a&gt; as to why I made this and how it progressed.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>askme - a mini utility for prompting questions</title>
            <pubDate>Fri, 27 Nov 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/askme/</link>
            <guid>https://abdus.dev/projects/askme/</guid>
            <description>&lt;p&gt;&lt;strong&gt;askme&lt;&#x2F;strong&gt; is a mini utility that prompts user questions and prints answers in key-value format to the console.&#xA;Questions are provided as command arguments as &lt;code&gt;&amp;quot;question&amp;quot;&lt;&#x2F;code&gt; or as &lt;code&gt;&amp;quot;question=default answer&amp;quot;&lt;&#x2F;code&gt; format. You can also specify a key with &lt;code&gt;key:&lt;&#x2F;code&gt; prefix.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;You can find the &lt;a href=&#34;https://github.com/abdusco/askme/releases&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;latest release&lt;&#x2F;a&gt; on Github:&lt;&#x2F;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;a class=&#34;repo-card&#34; href=&#34;https://github.com/abdusco/askme&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&#xA;    &lt;span class=&#34;repo-card__name&#34;&gt;&#xA;        &lt;span class=&#34;repo-card__owner&#34;&gt;abdusco&lt;&#x2F;span&gt;&#xA;        &lt;span aria-hidden=&#34;true&#34;&gt;/&lt;&#x2F;span&gt;&#xA;        &lt;strong&gt;askme&lt;&#x2F;strong&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__description&#34;&gt;A mini tool to prompt questions in a dialog and print the answers to console.&lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__meta&#34;&gt;&#xA;        &#xA;        &lt;span&gt;C#&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span aria-label=&#34;1 stars&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-star&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            1&#xA;        &lt;&#x2F;span&gt;&#xA;        &lt;span aria-label=&#34;1 forks&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-fork&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            1&#xA;        &lt;&#x2F;span&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;&lt;&#x2F;a&gt;&#xA;&#xA;&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;&#x2F;h2&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;askme&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;exe&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;filename:Filename for the export=export.zip&amp;#34;&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;author:Author?&amp;#34;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;year&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;A dialog should appear with inputs to type the answers:&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&lt;video src=&#34;./askme.webm&#34; autoplay loop controls playsinline&gt;&lt;&#x2F;video&gt;&lt;&#x2F;p&gt;&#xA;&lt;p&gt;When saved by clicking the Save button or hitting &lt;kbd&gt;ENTER&lt;&#x2F;kbd&gt; key, the app exits, and the user inputs are printed to console as JSON.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;&amp;#34;filename&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;report.zip&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;&amp;#34;author&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;abdus&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-nt&#34;&gt;&amp;#34;year&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;2020&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;The response can then be consumed from any script by reading the stdout and deserializing the JSON.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Hitting &lt;kbd&gt;Esc&lt;&#x2F;kbd&gt; key closes the window and nothing is printed to console.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;return-codes&#34;&gt;Return codes&lt;&#x2F;h2&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th align=&#34;right&#34;&gt;Code&lt;&#x2F;th&gt;&#xA;&lt;th align=&#34;left&#34;&gt;Reason&lt;&#x2F;th&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;&#x2F;thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td align=&#34;right&#34;&gt;&lt;code&gt;0&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td align=&#34;left&#34;&gt;User submitted the answers&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td align=&#34;right&#34;&gt;&lt;code&gt;1&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td align=&#34;left&#34;&gt;Failed to parse questions&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td align=&#34;right&#34;&gt;&lt;code&gt;2&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td align=&#34;left&#34;&gt;User cancelled the prompt&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;&#x2F;tbody&gt;&#xA;&lt;&#x2F;table&gt;&#xA;</description>
        </item>
        <item>
            <title>Getting the public URL for an ASP.NET app behind a reverse proxy</title>
            <pubDate>Sat, 14 Nov 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/getting-public-url-behind-reverse-proxy/</link>
            <guid>https://abdus.dev/posts/getting-public-url-behind-reverse-proxy/</guid>
            <description>&lt;p&gt;I&#x27;m building a small library for broadcasting URLs to a service that pings these URLs at regular intervals.&#xA;The service works like crontab on steroids.&#xA;It keeps a list of URLs and when those URLs should be called using cron expressions.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Each URL is handled by a handler that runs a job in the background.&#xA;This lets me perform routine tasks without having to run a separate worker process.&#xA;This approach has its faults, but for short &amp;amp; light tasks, it&#x27;s more than enough.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;At startup, I gather a list of cron jobs inside a &lt;code&gt;HostedService&lt;&#x2F;code&gt;,&#xA;then broadcast them to crontab-service to have them pinged later.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;problem-reverse-proxies&#34;&gt;Problem: reverse proxies&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;A problem I faced while building the URLs was to find out the public-facing URL for the web app.&#xA;I can&#x27;t just get these URLs from Kestrel or any other component because my app will be listening to localhost at some port.&#xA;App has no idea about whether there&#x27;s a server proxying requests to it, such as IIS, Caddy, Nginx, etc.&#xA;It has to wait until the first request to find out the actual URL that reaches the app.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;A workaround I&#x27;ve implemented was to supply a URL template &lt;code&gt;https://public.url/-/cronjobs/{name}&lt;&#x2F;code&gt; and generate URLs with it.&#xA;But if the URL is out of my control, as in the case of a lambda function with a random URL assigned by a cloud provider,&#xA;I would still have to get the URL.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;When working inside an HTTP context, I can find out which URL is being handled using&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;url&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Request&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetDisplayUrl&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;But a background service works outside the HTTP context, so it still cannot know about any HTTP event.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;solution&#34;&gt;Solution&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;The solution I implemented was to create a singleton to store the URL,&#xA;and put a middleware at the beginning of the request pipeline.&#xA;When the first request arrives, I trigger an event, and the worker waiting for this event continues execution.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;The singleton service is a wrapper around &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/api/system.threading.manualresetevent&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;ManualResetEventSlim&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;PublicUrlRegistry&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;private&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;static&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;readonly&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Lazy&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ManualResetEventSlim&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;@event&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Lazy&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ManualResetEventSlim&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;();&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;private&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;static&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Uri&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;_url&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;void&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Set&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Uri&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;url&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;_url&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;url&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;@event&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Value&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Set&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-kt&#34;&gt;bool&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IsSet&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;@event&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Value&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IsSet&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Uri&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;WaitForUrl&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CancellationToken&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;cancellationToken&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;@event&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Value&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Wait&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;cancellationToken&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;_url&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;The middleware retrieves and stores the URL of the request.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&#xA;&lt;span class=&#34;z-kd&#34;&gt;internal&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;static&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;ApplicationBuilderExtensions&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;static&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IApplicationBuilder&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;UsePublicUrlDetector&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;this&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IApplicationBuilder&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;app&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;app&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Use&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;((&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;next&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;registry&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;RequestServices&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetRequiredService&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PublicUrlRegistry&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;();&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;registry&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IsSet&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;next&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;registry&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Set&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Uri&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Request&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetDisplayUrl&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()));&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;next&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;});&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Background service waits until the URL is set, then continues execution.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;BackgroundWorker&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;BackgroundService&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;private&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;readonly&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ILogger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BackgroundWorker&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;_logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;private&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;readonly&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;PublicUrlRegistry&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;_registry&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;BackgroundWorker&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ILogger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BackgroundWorker&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;PublicUrlRegistry&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;registry&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;_logger&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;_registry&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;registry&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;protected&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;override&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ExecuteAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CancellationToken&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;stoppingToken&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;_logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;LogInformation&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;Waiting for the first request&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;try&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-c1&#34;&gt;// this line is blocking execution until event is set&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-kt&#34;&gt;var&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;url&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;_registry&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;WaitForUrl&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stoppingToken&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;_logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;LogInformation&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;Got {url}&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;url&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetLeftPart&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;UriPartial&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Authority&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;));&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;catch&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;OperationCanceledException&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-c1&#34;&gt;// App is stopping&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Task&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CompletedTask&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;and configured in &lt;code&gt;Startup&lt;&#x2F;code&gt; class. We need to register service as singleton,&#xA;and put middleware at the beginning of the request pipeline.&#xA;Otherwise, other middlewares could short circuit and requests may never reach our middleware.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;Startup&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-c1&#34;&gt;// ...&lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;void&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ConfigureServices&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IServiceCollection&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// not registering hosted service here! read on.&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddSingleton&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PublicUrlRegistry&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;();&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;void&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Configure&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IApplicationBuilder&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;app&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IWebHostEnvironment&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;env&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c1&#34;&gt;// middleware comes first!&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;app&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;UsePublicUrlDetector&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span class=&#34;z-n&#34;&gt;app&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;UseRouting&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;app&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;UseEndpoints&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;endpoints&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;endpoints&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;MapGet&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;/&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;context&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;context&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Response&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;WriteAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s&#34;&gt;&amp;#34;hello&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;));&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;});&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;deadlock&#34;&gt;Deadlock&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;So was the plan.&#xA;But after running the app I realized that the app never starts listening for requests.&#xA;Because it waits until &lt;code&gt;ExecuteAsync&lt;&#x2F;code&gt; method of the hosted service returns.&#xA;But that never happens because service waits until the first request.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-c1&#34;&gt;// Startup.cs&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;void&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ConfigureServices&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;IServiceCollection&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddHostedService&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BackgroundWorker&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;();&lt;&#x2F;span&gt; &lt;span class=&#34;z-c1&#34;&gt;// &amp;lt;-- won&amp;#39;t work!&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;services&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddSingleton&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PublicUrlRegistry&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;();&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We&#x27;ve got a &lt;a href=&#34;https://en.wikipedia.org/wiki/Deadlock&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;deadlock&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;PS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;dotnet&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;run&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;Building&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;...&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;info&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;WaitForFirstRequestDemo&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BackgroundWorker&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-n&#34;&gt;Waiting&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;the&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;first&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;request&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;making-sure-hosted-service-starts-after-the-server-starts-listening&#34;&gt;Making sure hosted service starts after the server starts listening&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;The solution comes from &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-5.0&amp;amp;tabs=visual-studio#ihostedservice-interface&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;IHostedService&#x27;s docs&lt;&#x2F;a&gt;.&#xA;If we register hosted service in Startup class then it runs before the app starts listening to requests.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We need to move the registration to where &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-5.0&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;IHost&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is built, i.e. &lt;code&gt;Program&lt;&#x2F;code&gt; class, instead.&#xA;We need to call &lt;code&gt;ConfigureServices&lt;&#x2F;code&gt; after &lt;code&gt;ConfigureWebHostDefaults&lt;&#x2F;code&gt; and register the service there.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c#&#34; data-highlighted=&#34;true&#34; class=&#34;language-c# z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;Program&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;static&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;void&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Main&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kt&#34;&gt;string&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[]&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;CreateHostBuilder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;).&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Build&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;().&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Run&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;();&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-kd&#34;&gt;public&lt;&#x2F;span&gt; &lt;span class=&#34;z-kd&#34;&gt;static&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;IHostBuilder&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CreateHostBuilder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kt&#34;&gt;string&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[]&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;Host&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CreateDefaultBuilder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ConfigureWebHostDefaults&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;webBuilder&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;webBuilder&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;UseStartup&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Startup&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;();&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;})&lt;&#x2F;span&gt; &lt;span class=&#34;z-c1&#34;&gt;// &amp;lt;-- first configure web&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ConfigureServices&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;collection&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;collection&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AddHostedService&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BackgroundWorker&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;());&lt;&#x2F;span&gt; &lt;span class=&#34;z-c1&#34;&gt;// &amp;lt;-- then register hosted service&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;When I run the app and send a request, everything works as it should.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;PS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;dotnet&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;run&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;Building&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;...&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;info&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Microsoft&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Hosting&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Lifetime&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-n&#34;&gt;Now&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;listening&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;on&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;https&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;//&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;localhost&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;5001&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;info&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Microsoft&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Hosting&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Lifetime&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-n&#34;&gt;Now&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;listening&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;on&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;http&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;//&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;localhost&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;5000&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;info&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;WaitForFirstRequestDemo&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BackgroundWorker&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-n&#34;&gt;Waiting&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;the&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;first&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;request&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;info&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Microsoft&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;AspNetCore&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Hosting&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Diagnostics&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;1&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-n&#34;&gt;Request&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;starting&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;HTTP&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;/&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;1.1&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;GET&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;https&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;//&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;localhost&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;5001&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;/&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;-&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;-&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;info&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;WaitForFirstRequestDemo&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BackgroundWorker&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;      &lt;span class=&#34;z-n&#34;&gt;Got&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;https&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;//&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;localhost&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;5001&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;</description>
        </item>
        <item>
            <title>Updating PATH environment variable using PowerShell</title>
            <pubDate>Tue, 27 Oct 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/update-path-powershell/</link>
            <guid>https://abdus.dev/posts/update-path-powershell/</guid>
            <description>&lt;p&gt;I am writing a script to configure a fresh Windows installation to fit my workflow.&#xA;This usually involves installing &lt;a href=&#34;https://chocolatey.org/install&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;chocolatey&lt;&#x2F;a&gt; as my package manager&#xA;and installing programs I need. Along with it, I also need to restore settings for certain apps&#xA;and update the &lt;a href=&#34;https://en.wikipedia.org/wiki/PATH_(variable)&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;PATH&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; environment variable.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Until today I&#x27;ve always added paths manually, but it turns out, there&#x27;s an easier way.&#xA;&lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/api/system.environment.setenvironmentvariable&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Thanks to some googling&lt;&#x2F;a&gt;,&#xA;I was able to create this script that adds the given dir&#xA;to &lt;code&gt;PATH&lt;&#x2F;code&gt; environment variable for the current user.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-kd&#34;&gt;function&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Add-ToUserPath&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;param&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Parameter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-na&#34;&gt;Mandatory&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;$true&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)]&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;ValidateNotNullOrEmpty&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()]&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;string&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt; &#xA;        &lt;span class=&#34;z-nv&#34;&gt;$dir&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-nv&#34;&gt;$dir&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Resolve-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$dir&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-nv&#34;&gt;$path&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;Environment&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetEnvironmentVariable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;PATH&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;System.EnvironmentVariableTarget&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;User&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(!(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Contains&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$dir&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)))&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c&#34;&gt;# backup the current value&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;PATH=&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$path&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;|&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;Set-Content&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$env:USERPROFILE&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;/path.env&amp;#34;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-c&#34;&gt;# append dir to path&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;Environment&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;SetEnvironmentVariable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;PATH&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$path&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;+&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$dir&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;EnvironmentVariableTarget&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;User&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nb&#34;&gt;Write-Host&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;Added &lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$dir&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt; to PATH&amp;#34;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nb&#34;&gt;Write-Error&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$dir&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt; is already in PATH&amp;#34;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;It expects a path to a directory as the argument:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-nb&#34;&gt;Add-ToUserPath&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$env:USERPROFILE&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;/.bin&amp;#34;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;If you want to set the environment variable for all users, change the target &lt;code&gt;[System.EnvironmentVariableTarget]&lt;&#x2F;code&gt; parameter from &lt;code&gt;User&lt;&#x2F;code&gt; to &lt;code&gt;Machine&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;adding-the-script-to-powershell-profile&#34;&gt;Adding the script to PowerShell profile&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Pasting and running this code, or importing a file every time we need to update &lt;code&gt;PATH&lt;&#x2F;code&gt; defeats the purpose.&#xA;We need a way to access it quickly. That&#x27;s where the PowerShell &lt;a href=&#34;https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;profile&lt;&#x2F;a&gt; comes into play.&#xA;It&#x27;s a file (similar to &lt;code&gt;.bashrc&lt;&#x2F;code&gt; in bash) that runs everytime a PowerShell session starts&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Open the profile with an editor.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;notepad&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$PROFILE&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Paste the function into this file and save it.&#xA;When you open a new powershell window, you should be able to use the function.&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr /&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Unless &lt;code&gt;-NoProfile&lt;&#x2F;code&gt; argument is given&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ol&gt;&#xA;&lt;&#x2F;div&gt;&#xA;</description>
        </item>
        <item>
            <title>Fixing WSL2 localhost access issue</title>
            <pubDate>Sun, 02 Aug 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/fixing-wsl2-localhost-access-issue/</link>
            <guid>https://abdus.dev/posts/fixing-wsl2-localhost-access-issue/</guid>
            <description>&lt;p&gt;I&#x27;ve been using &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/wsl/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;WSL&lt;&#x2F;a&gt; ever since it came out. It was nice being able to run Linux on Windows without the overhead of VMs. But it had some issues, like not being able to run Docker natively. Microsoft then released a new version called WSL2 which has brought native Docker support.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;One feature of WSL is that it allows sharing IP address space for services listening to &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/wsl/faq#how-do-i-access-a-port-from-wsl-in-windows&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;localhost&lt;&#x2F;a&gt;. This means one can access servers running on WSL from Windows as if it were running on Windows. This lets us, for instance, access a Docker container listening to &lt;code&gt;0.0.0.0:8000&lt;&#x2F;code&gt; on WSL from Windows using &lt;code&gt;localhost:8000&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;problem&#34;&gt;Problem&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Localhost redirection &lt;a href=&#34;https://github.com/microsoft/WSL/issues?q=is%3Aissue+is%3Aopen+localhost&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;often fails&lt;&#x2F;a&gt; for some reason, such as when PC sleeps and wakes up, and localhost access to Linux services does not work anymore.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;solution&#34;&gt;Solution&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;We can get the internal IP a WSL instance (in my case an Ubuntu distro) has using&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;bash&#34; data-highlighted=&#34;true&#34; class=&#34;language-bash z-code z-chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;$ ip -4 addr show eth0&#xA;5: eth0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu &lt;span class=&#34;z-m&#34;&gt;1500&lt;&#x2F;span&gt; qdisc mq state UP group default qlen &lt;span class=&#34;z-m&#34;&gt;1000&lt;&#x2F;span&gt;&#xA;    inet 172.27.106.75/20 brd 172.27.111.255 scope global eth0&#xA;       valid_lft forever preferred_lft forever&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We can see the IP address &lt;code&gt;172.27.106.75&lt;&#x2F;code&gt; which we can ping from Windows just fine.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;PS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ping&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;172.27&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;106&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;75&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-n&#34;&gt;Pinging&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;172.27&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;106&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;75&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;with&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;32&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bytes&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;of&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;data&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;Reply&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;172.27&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;106&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;75&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;bytes&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;32&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;time&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;1ms&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;TTL&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;64&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We can run a Python server and reach it from Windows.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;bash&#34; data-highlighted=&#34;true&#34; class=&#34;language-bash z-code z-chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;$ python3 -m http.server&#xA;Serving HTTP on 0.0.0.0 port &lt;span class=&#34;z-m&#34;&gt;8000&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;(&lt;&#x2F;span&gt;http://0.0.0.0:8000/&lt;span class=&#34;z-o&#34;&gt;)&lt;&#x2F;span&gt; ...&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;PS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;curl &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;-I&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;http&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;//&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;172.27&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;106&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;75&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;8000&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;HTTP&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;/&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;1.0&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;200&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;OK&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now we can make the experience better by not having to type in the full IP address everytime we want to access WSL. We can use the &lt;a href=&#34;https://en.wikipedia.org/wiki/Hosts_(file)&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;hosts&lt;&#x2F;code&gt; file&lt;&#x2F;a&gt; for that. But there&#x27;s a catch: we can&#x27;t just use &lt;code&gt;localhost&lt;&#x2F;code&gt;, because that&#x27;d break a lot of systems that rely on &lt;code&gt;localhost&lt;&#x2F;code&gt; working as a loopback address. That&#x27;s why I chose &lt;code&gt;wsl&lt;&#x2F;code&gt; as the hostname, you&#x27;re free to use something else.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;For this I&#x27;ve written a PowerShell script that gets the IP address of WSL instance, then adds (or updates) an entry in the &lt;code&gt;hosts&lt;&#x2F;code&gt; file.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-nv&#34;&gt;$hostname&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;wsl&amp;#34;&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-c&#34;&gt;# find ip of eth0&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$ifconfig&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;wsl&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;--&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ip&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;-4&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;addr&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;show&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;eth0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$ipPattern&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;((\d+\.?){4})&amp;#34;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$ip&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;([&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;regex&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;inet &lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$ipPattern&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;).&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Match&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$ifconfig&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;).&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Groups&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;1&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;].&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Value&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;-not&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$ip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;exit&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nb&#34;&gt;Write-Host&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$ip&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$hostsPath&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$env:windir&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;/system32/drivers/etc/hosts&amp;#34;&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$hosts&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Get-Content&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$hostsPath&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-Raw&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-ErrorAction&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Ignore&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;$null&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-eq&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$hosts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nv&#34;&gt;$hosts&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$hosts&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$hosts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Trim&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-c&#34;&gt;# update or add wsl ip&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$find&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$ipPattern&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;\s+&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$hostname&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$entry&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$ip&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$hostname&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$hosts&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-match&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$find&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nv&#34;&gt;$hosts&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$hosts&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-replace&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$find&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$entry&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;else&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nv&#34;&gt;$hosts&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$hosts&lt;&#x2F;span&gt;&lt;span class=&#34;z-se&#34;&gt;`n&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$entry&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Trim&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;try&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nv&#34;&gt;$temp&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$hostsPath&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;.new&amp;#34;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nb&#34;&gt;New-Item&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$temp&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-ItemType&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;File&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-Force&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;|&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;Out-Null&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nb&#34;&gt;Set-Content&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$temp&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$hosts&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-nb&#34;&gt;Move-Item&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$temp&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-Destination&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$hostsPath&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-Force&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;catch&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nb&#34;&gt;Write-Error&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;cannot update wsl ip&amp;#34;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Save this script as &lt;code&gt;wsl.ps1&lt;&#x2F;code&gt; and run it as admin to add an entry for WSL in hosts file.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;cmd&#34; data-highlighted=&#34;true&#34; class=&#34;language-cmd z-code z-chroma&#34;&gt;&lt;code class=&#34;language-cmd&#34; data-lang=&#34;cmd&#34;&gt;powershell -file wsl.ps1&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;automating-ip-renewal&#34;&gt;Automating IP renewal&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Everytime a WSL instance restarts, it gets a new IP address, so we need to keep the hosts file updated.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;PS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wsl&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;--&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ip&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;-4&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;addr&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;show&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;eth0&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-mf&#34;&gt;5&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;eth0&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BROADCAST&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;MULTICAST&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;UP&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;LOWER_UP&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;mtu&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;1500&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;qdisc&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;mq&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;state&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;UP&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;group &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;default&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;qlen&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;1000&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;inet&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;172.27&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;101&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;9&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;/&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;20&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;brd&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;172.27&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;111&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;255&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;scope&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;global&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;eth0&lt;&#x2F;span&gt;&#xA;       &lt;span class=&#34;z-n&#34;&gt;valid_lft&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;forever&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;preferred_lft&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;forever&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;PS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wsl&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;-&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;-shutdown&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;PS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wsl&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;--&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ip&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;-4&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;addr&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;show&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;eth0&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-mf&#34;&gt;5&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;eth0&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;BROADCAST&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;MULTICAST&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;UP&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;LOWER_UP&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;mtu&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;1500&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;qdisc&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;mq&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;state&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;UP&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;group &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;default&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;qlen&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;1000&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;inet&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;172.27&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;102&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;184&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;/&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;20&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;brd&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;172.27&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;111&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;255&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;scope&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;global&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;eth0&lt;&#x2F;span&gt;&#xA;       &lt;span class=&#34;z-n&#34;&gt;valid_lft&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;forever&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;preferred_lft&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;forever&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We can use Scheduled Tasks to monitor WSL network events and have it run our script everytime it&#x27;s assigned a new IP address.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Watching new entries Event Viewer under &lt;strong&gt;Windows Logs &amp;gt; System&lt;&#x2F;strong&gt; for &lt;strong&gt;Hyper-V&lt;&#x2F;strong&gt;, we can see a couple of new entries when we restart WSL.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/posts/fixing-wsl2-localhost-access-issue/wsl-event.png&#34; alt=&#34;&#34; /&gt;&lt;&#x2F;p&gt;&#xA;&lt;p&gt;The last entry about loading networking driver seems like a good trigger for our script.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Right click the log and click &lt;strong&gt;Attach Task To This Event&lt;&#x2F;strong&gt; and follow the wizard. Choose &lt;strong&gt;Start a program&lt;&#x2F;strong&gt; as an action and type&lt;&#x2F;p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Program: &lt;code&gt;powershell.exe&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Add arguments: &lt;code&gt;-file c:/path/to/wsl.ps1&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;&lt;p&gt;and complete the wizard.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Open Scheduled Tasks and go to &lt;strong&gt;Task Scheduler Library &amp;gt; Event Viewer Tasks&lt;&#x2F;strong&gt;. Find the task we&#x27;ve just created and open its properties.&lt;&#x2F;p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;In &lt;strong&gt;General&lt;&#x2F;strong&gt; tab, check &lt;strong&gt;Run with highest privileges&lt;&#x2F;strong&gt;. Writing inside &lt;code&gt;%WINDIR%&lt;&#x2F;code&gt; requires admin privileges, or the script would fail to run.&lt;&#x2F;li&gt;&#xA;&lt;li&gt;In &lt;strong&gt;Conditions&lt;&#x2F;strong&gt; tab, disable &lt;strong&gt;Start the task only if the computer is on AC power&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;&lt;p&gt;then save the task.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Run the task and check the hosts file to see if WSL IP address is added. Also restart WSL using:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-c&#34;&gt;# shutdown&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;wsl&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;-&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;-shutdown&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-c&#34;&gt;# start&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;wsl&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;A PowerShell window should pop up and update the IP address.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Now we can access servers running on WSL by &lt;code&gt;wsl:PORT&lt;&#x2F;code&gt; when &lt;code&gt;localhost:PORT&lt;&#x2F;code&gt; stops working.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>Quick full-text search using SQLite</title>
            <pubDate>Thu, 02 Jul 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/quick-full-text-search-using-sqlite/</link>
            <guid>https://abdus.dev/posts/quick-full-text-search-using-sqlite/</guid>
            <description>&lt;p&gt;SQLite provides full-text search functionality with &lt;a href=&#34;https://www.sqlite.org/fts5.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;FTS5 extension&lt;&#x2F;a&gt; that lets us index columns of a table&#xA;then perform searches using keywords against the table.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;creating-fts-tables&#34;&gt;Creating FTS tables&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;We&#x27;ll start with a common scenario, where we keep a set of blog posts in a &lt;code&gt;posts&lt;&#x2F;code&gt; table:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;sql&#34; data-highlighted=&#34;true&#34; class=&#34;language-sql z-code z-chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;z-k&#34;&gt;CREATE&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;TABLE&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;id&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;INTEGER&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;NOT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;NULL&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;PRIMARY&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;KEY&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;AUTOINCREMENT&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;TEXT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;NOT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;NULL&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;body&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;  &lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;TEXT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;NOT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;NULL&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;We want to conduct searches on &lt;code&gt;title&lt;&#x2F;code&gt; and &lt;code&gt;body&lt;&#x2F;code&gt; columns of a post.&#xA;Then the corresponding FTS table definition would be:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;sql&#34; data-highlighted=&#34;true&#34; class=&#34;language-sql z-code z-chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;z-k&#34;&gt;CREATE&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;VIRTUAL&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;TABLE&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts_fts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;USING&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;fts5&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;body&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;content&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Here &lt;code&gt;content=posts&lt;&#x2F;code&gt; indicate that SQLite will query &lt;code&gt;posts&lt;&#x2F;code&gt; table whenever it needs to access column values.&#xA;By default SQLite uses &lt;code&gt;rowid&lt;&#x2F;code&gt; column to find the row it needs,&#xA;but an optional &lt;code&gt;content_rowid=id&lt;&#x2F;code&gt; parameter can be specified to instruct to use the preferred key column &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;keeping-fts-index-up-to-date&#34;&gt;Keeping FTS index up-to-date&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;We&#x27;ve set up FTS indices, but we need to keep it current,&#xA;and for that we&#x27;ll create a couple of triggers that hook into &lt;code&gt;posts&lt;&#x2F;code&gt; table&#xA;and add/update/remove content from the index:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;sql&#34; data-highlighted=&#34;true&#34; class=&#34;language-sql z-code z-chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;z-k&#34;&gt;CREATE&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;TRIGGER&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts_fts_insert&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;AFTER&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;INSERT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;ON&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;BEGIN&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;INSERT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;INTO&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts_fts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;rowid&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;body&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;VALUES&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;rowid&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;body&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;END&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;CREATE&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;TRIGGER&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts_fts_delete&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;AFTER&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;DELETE&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;ON&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;BEGIN&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;INSERT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;INTO&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts_fts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts_fts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;rowid&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;body&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;VALUES&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;delete&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;old&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;rowid&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;old&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;old&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;body&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;END&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;CREATE&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;TRIGGER&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts_fts_update&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;AFTER&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;UPDATE&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;ON&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;BEGIN&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;INSERT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;INTO&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts_fts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts_fts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;rowid&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;body&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;VALUES&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;delete&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;old&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;rowid&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;old&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;old&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;body&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;    &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;INSERT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;INTO&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts_fts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;rowid&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;body&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;VALUES&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;rowid&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;new&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;body&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;END&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now we can insert new records into &lt;code&gt;posts&lt;&#x2F;code&gt; table and FTS index will be populated automatically.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;sql&#34; data-highlighted=&#34;true&#34; class=&#34;language-sql z-code z-chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;z-k&#34;&gt;INSERT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;INTO&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;body&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;VALUES&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;I daresay that Fry has discovered the smelliest object in the known universe!&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;        &lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Soothe us with sweet lies. You&amp;#39;&amp;#39;ve killed me! Oh, you&amp;#39;&amp;#39;ve killed me! Good news, everyone! There&amp;#39;&amp;#39;s a report on TV with some very bad news! Look, last night was a mistake.&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;       &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Have you ever tried just turning off the TV, sitting down with your children, and hitting them?&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;        &lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Yes. You gave me a dollar and some candy. Professor, make a woman out of me. You seem malnourished. Are you suffering from intestinal parasites? Is the Space Pope reptilian!? I don&amp;#39;&amp;#39;t &amp;#39;&amp;#39;need&amp;#39;&amp;#39; to drink. I can quit anytime I want!&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;querying-fts-table&#34;&gt;Querying FTS table&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Now we can perform full-text searches using FTS table to find matching records, then get the content for those.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;sql&#34; data-highlighted=&#34;true&#34; class=&#34;language-sql z-code z-chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;z-k&#34;&gt;SELECT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;FROM&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;WHERE&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ROWID&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;IN&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;SELECT&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ROWID&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;FROM&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts_fts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;WHERE&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;posts_fts&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;MATCH&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;fry&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;ORDER&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-k&#34;&gt;BY&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt; &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;rank&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&lt;span class=&#34;z-w&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Here we use &lt;code&gt;&amp;lt;fts_table&amp;gt; match &#x27;term&#x27;&lt;&#x2F;code&gt; to search FTS table&#xA;and order the results according to their relevance using &lt;code&gt;rank&lt;&#x2F;code&gt; value.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;this gives us a match as expected:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;commandline&#34; data-highlighted=&#34;true&#34; class=&#34;language-commandline z-code z-chroma&#34;&gt;&lt;code class=&#34;language-commandline&#34; data-lang=&#34;commandline&#34;&gt;sqlite&amp;gt; .mode line&#xA;sqlite&amp;gt; SELECT *&#xA;   ...&amp;gt; FROM posts&#xA;   ...&amp;gt; WHERE ROWID IN (SELECT ROWID FROM posts_fts WHERE posts_fts MATCH &amp;#39;fry&amp;#39; ORDER BY rank);&#xA;   id = 1&#xA;title = I daresay that Fry has discovered the smelliest object in the known universe!&#xA; body = Soothe us with sweet lies. You&amp;#39;ve killed me! Oh, you&amp;#39;ve killed me! Good news, everyone! There&amp;#39;s a report on TV with some very bad news! Look, last night was a mistake.&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;SQLite supports a couple of operators in addition to plain search, such as &lt;code&gt;AND&lt;&#x2F;code&gt;, &lt;code&gt;OR&lt;&#x2F;code&gt;, &lt;code&gt;NOT&lt;&#x2F;code&gt;, verbatim &lt;code&gt;&amp;quot;term&amp;quot;&lt;&#x2F;code&gt; &lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;python-script&#34;&gt;Python script&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Here&#x27;s a python script to enable FTS for a table and search against it. Loosely inspired by &lt;a href=&#34;https://github.com/simonw/sqlite-utils&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;sqlite-utils&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;sqlite3&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;sqlite3&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Connection&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;typing&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;enable_fts&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Connection&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;table&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;columns&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]):&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;column_list&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;,&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;join&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-sa&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;c&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;]&amp;#39;&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;c&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;columns&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;executescript&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;&amp;#39;&amp;#39;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        CREATE VIRTUAL TABLE [&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts] USING fts5&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        (&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;            &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{column_list}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;,&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;            content=[&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts]&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        )&amp;#39;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;format&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;table&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;table&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;column_list&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;column_list&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-n&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;executescript&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;&amp;#39;&amp;#39;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        CREATE TRIGGER [&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts_insert] AFTER INSERT ON posts&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        BEGIN&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;            INSERT INTO [&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts] (rowid, &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{column_list}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;) VALUES (new.rowid, &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{new_columns}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;);&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        END;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        CREATE TRIGGER [&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts_delete] AFTER DELETE ON posts&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        BEGIN&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;            INSERT INTO [&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts] ([&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts], rowid, &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{column_list}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;) VALUES (&amp;#39;delete&amp;#39;, old.rowid, &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{old_columns}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;);&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        END;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        CREATE TRIGGER [&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts_update] AFTER UPDATE ON posts&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        BEGIN&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;            INSERT INTO [&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts] ([&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts], rowid, &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{column_list}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;) VALUES (&amp;#39;delete&amp;#39;, old.rowid, &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{old_columns}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;);&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;            INSERT INTO [&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts] (rowid, &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{column_list}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;) VALUES (new.rowid, &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{new_columns}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;);&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        END;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;format&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;table&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;table&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;column_list&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;column_list&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;new_columns&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;,&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;join&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-sa&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;new.[&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;c&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;]&amp;#39;&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;c&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;columns&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;old_columns&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;,&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;join&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-sa&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;old.[&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;c&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;]&amp;#39;&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;c&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;columns&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;query&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Connection&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;table&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;term&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;sqlite3&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Row&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;cur&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;db&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;execute&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;&amp;#39;&amp;#39;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        SELECT * FROM [&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;]&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;        WHERE ROWID IN (SELECT ROWID FROM [&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts] WHERE [&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{table}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;_fts] MATCH ? ORDER BY rank)&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;format&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;table&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;table&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;term&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;])&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;list&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;cur&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;fetchall&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;())&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr /&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;More on &lt;a href=&#34;https://www.sqlite.org/fts5.html#external_content_tables&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;external content&lt;&#x2F;a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;More on &lt;a href=&#34;https://www.sqlite.org/fts5.html#full_text_query_syntax&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;search syntax&lt;&#x2F;a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ol&gt;&#xA;&lt;&#x2F;div&gt;&#xA;</description>
        </item>
        <item>
            <title>Building a UI with vanilla Javascript</title>
            <pubDate>Wed, 01 Jul 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/building-a-ui-with-vanilla-javascript/</link>
            <guid>https://abdus.dev/posts/building-a-ui-with-vanilla-javascript/</guid>
            <description>&lt;p&gt;I enjoy building server-rendered apps. They&#x27;re faster to build, more straightforward, and easy to get your head around.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;One tool I keep reaching for is &lt;a href=&#34;https://github.com/alpinejs/alpine/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Alpine JS&lt;&#x2F;a&gt;, which is a tiny library that helps to build components on a server-rendered page with a Vue-like syntax.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;But it does not apply to all needs, or sometimes a couple of lines of vanilla JS is all the page ever needs. In those times, I write this utility function:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;js&#34; data-highlighted=&#34;true&#34; class=&#34;language-js z-code z-chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;z-cm&#34;&gt;/**&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; * @param {string} name&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; * @param {HTMLElement.prototype} props&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; * @param {Array&amp;lt;HTMLElement|string&amp;gt;} children&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; * @return HTMLElement&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-cm&#34;&gt; */&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kd&#34;&gt;function&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;$h&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;name&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;div&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;props&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{},&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;children&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[])&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;el&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;document&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;createElement&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;name&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nb&#34;&gt;Object&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;assign&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;el&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;props&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nx&#34;&gt;el&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;append&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(...&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;children&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;el&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;and use it like so:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;js&#34; data-highlighted=&#34;true&#34; class=&#34;language-js z-code z-chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;z-kr&#34;&gt;const&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;button&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;$h&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;button&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nx&#34;&gt;className&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;button&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nx&#34;&gt;onclick&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-kr&#34;&gt;async&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;e&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nx&#34;&gt;alert&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;clicked&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Download&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]);&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-nb&#34;&gt;document&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;body&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;append&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nx&#34;&gt;button&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;It resembles &lt;a href=&#34;https://mithril.js.org/index.html#dom-elements&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Mithril&#x27;s syntax&lt;&#x2F;a&gt;, but it&#x27;s not reactive as modern UI libraries.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>Adding metadata to videos using ffmpeg</title>
            <pubDate>Mon, 29 Jun 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/ffmpeg-metadata/</link>
            <guid>https://abdus.dev/posts/ffmpeg-metadata/</guid>
            <description>&lt;p&gt;I&#x27;ve been maintaining a movie archive for years now. Every year or so I go over what I have and do a clean-up if necessary.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;This time I&#x27;ve decided to add metadata about the title, year, IMDb links, artists, etc. to videos. I&#x27;ve written about &lt;a href=&#34;/posts/powershell-file-metadata-guide/#video-metadata&#34;&gt;a way to do it on PowerShell&lt;&#x2F;a&gt;, but nothing comes closer to the joy I get from writing in Python.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;ffmpeg-command&#34;&gt;ffmpeg command&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;ffmpeg supports practically anything under the sun, including adding metadata. &lt;a href=&#34;https://stackoverflow.com/a/11479066/5298150&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;This answer&lt;&#x2F;a&gt; on StackOverflow put me on the right path. With ffmpeg, adding metadata is simply including &lt;code&gt;-metadata key=value&lt;&#x2F;code&gt; arguments in the command:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;ffmpeg&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-i&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;mkv&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-c&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;copy &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;-movflags&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;use_metadata_tags&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-map_metadata&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;0&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-metadata&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;Hello World&amp;#34;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-metadata&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;year&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;2020&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;metadata&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;mkv&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;when split into lines:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;ffmpeg&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-i&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;mkv&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;`&lt;&#x2F;span&gt; &#xA;    &lt;span class=&#34;z-n&#34;&gt;-c&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;copy &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;`&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;-movflags&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;use_metadata_tags&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;`&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;-map_metadata&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;0&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;`&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;-metadata&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;Hello World&amp;#34;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;`&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;-metadata&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;year&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-mf&#34;&gt;2020&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;`&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;metadata&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;mkv&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Some important points:&lt;&#x2F;p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Use &lt;code&gt;-c copy&lt;&#x2F;code&gt; to copy streams directly without encoding.&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Use &lt;code&gt;-map_metadata 0&lt;&#x2F;code&gt; to copy over existing meta tags without changing them&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;&lt;p&gt;Not all containers support every metadata. For reference you can check &lt;a href=&#34;https://www.matroska.org/technical/tagging.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Matroska spec&lt;&#x2F;a&gt; for MKV and &lt;a href=&#34;https://kodi.wiki/view/Video_file_tagging#Supported_Tags&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Kodi docs&lt;&#x2F;a&gt; for MP4.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;python-script&#34;&gt;Python script&lt;&#x2F;h2&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;subprocess&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;pathlib&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;typing&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Dict&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;add_metadata&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                 &lt;span class=&#34;z-n&#34;&gt;meta&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Dict&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;                 &lt;span class=&#34;z-n&#34;&gt;save_path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                 &lt;span class=&#34;z-n&#34;&gt;overwrite&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;bool&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;not&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;save_path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;save_path&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;video&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;with_suffix&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;.metadata&amp;#39;&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;+&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;video&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;suffix&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-n&#34;&gt;metadata_args&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[]&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;k&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;v&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;meta&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;items&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;metadata_args&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;extend&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;([&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-metadata&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-sa&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;k&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;v&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;])&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;ffmpeg&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-v&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;quiet&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-i&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;video&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;absolute&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-movflags&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;use_metadata_tags&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-map_metadata&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;0&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-o&#34;&gt;*&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;metadata_args&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-c&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;copy&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;save_path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;overwrite&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;append&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-y&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;run&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PIPE&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;check_returncode&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;vid&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-sa&#34;&gt;r&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;video.mp4&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;add_metadata&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;vid&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;meta&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;dict&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;title&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;vid&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stem&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;comment&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;vid&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stem&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;year&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-mi&#34;&gt;2020&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;&lt;code&gt;add_metadata&lt;&#x2F;code&gt; function takes arbitrary key:value parameters and turns them into ffmpeg arguments.&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr /&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;From &lt;a href=&#34;https://video.stackexchange.com/questions/23741/how-to-prevent-ffmpeg-from-dropping-metadata&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;this&lt;&#x2F;a&gt; StackOverflow answer&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ol&gt;&#xA;&lt;&#x2F;div&gt;&#xA;</description>
        </item>
        <item>
            <title>Detecting USB drive insertion &amp; removal on Windows using Python</title>
            <pubDate>Sun, 28 Jun 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/python-monitor-usb/</link>
            <guid>https://abdus.dev/posts/python-monitor-usb/</guid>
            <description>&lt;p&gt;I was looking for a way to monitor USB drives and trigger a backup when I plug in my backup drive.&#xA;Most solutions online describe a way to do it in C# or C++, but I wanted to write one in Python.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;#alternative-approach-polling-for-drives&#34;&gt;My first naive attempt&lt;&#x2F;a&gt; was to get the list of drives, and poll for changes every couple of seconds. I&#x27;ve explained this method below and it works fine.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;However I wasn&#x27;t really comfortable with launching a subprocess every second. It seemed wasteful.&#xA;So I&#x27;ve written a second method that uses Win32 APIs to hook into Windows and monitor drive changes.&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;has-recommend&#34;&gt;&#xA;&lt;p&gt;If you&#x27;re interested, I have &lt;a href=&#34;/posts/monitor-clipboard/&#34;&gt;another post&lt;&#x2F;a&gt; where I build a clipboard listener that you can use to monitor clipboard contents.&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;recommend&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;/posts/monitor-clipboard/&#34;&gt;Monitoring clipboard contents on Windows with Python&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;&#x2F;div&gt;&#xA;&lt;&#x2F;div&gt;&#xA;&lt;h2 id=&#34;getting-a-list-of-drives&#34;&gt;Getting a list of drives&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Windows provides an interface for sysadmins called &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-start-page&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Windows Management Instrumentation&lt;&#x2F;a&gt; (WMI) which basically allows you to monitor a local or remote system.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;On PowerShell we can use &lt;a href=&#34;https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-wmiobject&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;Get-WmiObject&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; command to interact with it.&#xA;WMI &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-logicaldisk&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;Win32_LogicalDisk&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; class gives us a list of the drives currently connected to the PC.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-nb&#34;&gt;PS &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;Get-WmiObject&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-Class&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Win32_LogicalDisk&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-n&#34;&gt;DeviceID&lt;&#x2F;span&gt;     &lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;C:&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;DriveType&lt;&#x2F;span&gt;    &lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;3&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;ProviderName&lt;&#x2F;span&gt; &lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;FreeSpace&lt;&#x2F;span&gt;    &lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;21341257728&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;Size&lt;&#x2F;span&gt;         &lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;130417745920&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;VolumeName&lt;&#x2F;span&gt;   &lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-p&#34;&gt;...&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-n&#34;&gt;DeviceID&lt;&#x2F;span&gt;     &lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;E:&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;DriveType&lt;&#x2F;span&gt;    &lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;2&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;ProviderName&lt;&#x2F;span&gt; &lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;FreeSpace&lt;&#x2F;span&gt;    &lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;48837689344&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;Size&lt;&#x2F;span&gt;         &lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;62706155520&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;VolumeName&lt;&#x2F;span&gt;   &lt;span class=&#34;z-err&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ABDUS&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;That&#x27;s a lot of useful information 👌. We can use &lt;a href=&#34;https://devblogs.microsoft.com/scripting/inventory-drive-types-by-using-powershell/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;DriveType&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; to determine if a drive is removable or not.&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;table&#34;&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th align=&#34;right&#34;&gt;DriveType&lt;&#x2F;th&gt;&#xA;&lt;th&gt;Description&lt;&#x2F;th&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;&#x2F;thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td align=&#34;right&#34;&gt;&lt;code&gt;0&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;Unknown&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td align=&#34;right&#34;&gt;&lt;code&gt;1&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;No Root Directory&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td align=&#34;right&#34;&gt;&lt;strong&gt;&lt;code&gt;2&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;strong&gt;Removable Disk&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td align=&#34;right&#34;&gt;&lt;code&gt;3&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;Local Disk&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td align=&#34;right&#34;&gt;&lt;code&gt;4&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;Network Drive&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td align=&#34;right&#34;&gt;&lt;code&gt;5&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;Compact Disc&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td align=&#34;right&#34;&gt;&lt;code&gt;6&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;RAM Disk&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;&#x2F;tbody&gt;&#xA;&lt;&#x2F;table&gt;&#xA;&lt;&#x2F;div&gt;&#xA;&lt;p&gt;Finally, we can pick the fields we need and convert the command result to JSON so that Python can easily parse it.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-nb&#34;&gt;PS &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;Get-WmiObject&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;-Class&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Win32_LogicalDisk&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;|&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;Select-Object&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;deviceid&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;volumename&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;drivetype&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;|&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;ConvertTo-Json&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;DeviceID&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;C:&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;VolumeName&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;DriveType&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;3&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;DeviceID&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;E:&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;VolumeName&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;ABDUS&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;DriveType&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;2&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-err&#34;&gt;...&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Now we can call this PowerShell command from Python using &lt;a href=&#34;https://docs.python.org/3/library/subprocess.html#subprocess.run&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;subprocess&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; module and parse its JSON output:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;dataclasses&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;dataclass&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;typing&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-nd&#34;&gt;@dataclass&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;letter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;label&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;drive_type&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-nd&#34;&gt;@property&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;is_removable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;bool&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;drive_type&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Removable Disk&amp;#39;&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;list_drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    Get a list of drives using WMI&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    :return: list of drives&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;run&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;powershell&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-noprofile&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-command&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Get-WmiObject -Class Win32_LogicalDisk | Select-Object deviceid,volumename,drivetype | ConvertTo-Json&amp;#39;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;text&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PIPE&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;returncode&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;!=&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;or&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;not&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;strip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Failed to enumerate drives&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[]&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;devices&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;json&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;loads&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-n&#34;&gt;drive_types&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Unknown&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;1&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;No Root Directory&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;2&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Removable Disk&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;3&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Local Disk&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;4&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Network Drive&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;5&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Compact Disc&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;6&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;RAM Disk&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;letter&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;deviceid&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;label&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;volumename&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;drive_type&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;drive_types&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;drivetype&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]]&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;devices&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;list_drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;())&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;which outputs&lt;&#x2F;p&gt;&#xA;&lt;pre&gt;&lt;code&gt;[Drive(letter=&#x27;C:&#x27;, label=&#x27;&#x27;, drive_type=&#x27;Local Disk&#x27;), ...]&#xA;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;OK. Now the difficult part: hooking it up to Windows.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;listening-to-windows-wm-devicechange-messages&#34;&gt;Listening to Windows &lt;code&gt;WM_DEVICECHANGE&lt;&#x2F;code&gt; messages&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Windows uses &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/learnwin32/window-messages&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;messages&lt;&#x2F;a&gt; to notify programs of events and let them react. This includes user inputs (mouse clicks, key strokes etc.) and other OS events (hardware connected, low power, A/C adapter connected etc.).&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Windows broadcasts a &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/devio/wm-devicechange#parameters&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;WM_DEVICECHANGE&lt;&#x2F;code&gt; message&lt;&#x2F;a&gt; when hardware configuration of the system changes. That includes plug-and-play devices, such as USB drives, printers, mouse etc. We need a way to listen to this broadcast. That requires creating a window, registering a window procedure, then running a message loop that receives messages from the operating system.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;We need the &lt;a href=&#34;https://github.com/mhammond/pywin32&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;pywin32&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; package, which provides extensions to consume Win32 APIs in Python. We can install it with pip:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;shell&#34; data-highlighted=&#34;true&#34; class=&#34;language-shell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;pip install pywin32&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;To create a window, we can follow &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/learnwin32/creating-a-window&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Microsoft&#x27;s docs&lt;&#x2F;a&gt; and translate the given C++ example to its Python equivalent.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32api&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32gui&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;create_window&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    Create a window for listening to messages&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexa#example&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    &#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    See also: https://docs.microsoft.com/en-us/windows/win32/learnwin32/creating-a-window#creating-the-window&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    :return: window hwnd&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;WNDCLASS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;lpfnWndProc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;lpszClassName&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Demo&amp;#39;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hInstance&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32api&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetModuleHandle&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;class_atom&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;RegisterClass&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CreateWindow&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;class_atom&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Demo&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hInstance&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;create_window&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PumpMessages&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;The highlighted line is the critical part. It is our &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/winmsg/using-window-procedures&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&amp;quot;window procedure&amp;quot;&lt;&#x2F;a&gt; that gets called when the window receives a message.&#xA;It must have the following signature:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;c++&#34; data-highlighted=&#34;true&#34; class=&#34;language-c++ z-code z-chroma&#34;&gt;&lt;code class=&#34;language-c++&#34; data-lang=&#34;c++&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;LRESULT&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;CALLBACK&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;MainWndProc&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;HWND&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;        &lt;span class=&#34;z-c1&#34;&gt;// handle to window&#xA;&lt;&#x2F;span&gt;    &lt;span class=&#34;z-n&#34;&gt;UINT&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;uMsg&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;        &lt;span class=&#34;z-c1&#34;&gt;// message identifier&#xA;&lt;&#x2F;span&gt;    &lt;span class=&#34;z-n&#34;&gt;WPARAM&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wParam&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;    &lt;span class=&#34;z-c1&#34;&gt;// first message parameter&#xA;&lt;&#x2F;span&gt;    &lt;span class=&#34;z-n&#34;&gt;LPARAM&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;lParam&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;    &lt;span class=&#34;z-c1&#34;&gt;// second message parameter&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;...&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Python&#x27;s &lt;code&gt;print&lt;&#x2F;code&gt; function accepts an arbitrary number of parameters and dumps all its arguments to console, so its signature is compatible.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Once we run this script we can see the messages received by our window:&lt;&#x2F;p&gt;&#xA;&lt;pre&gt;&lt;code&gt;789538 537 7 0&#xA;789538 537 32772 346174713248&#xA;...&#xA;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;All these integers are either a value or a pointer to a value. Let&#x27;s break down the second message:&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;table&#34;&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;Parameter&lt;&#x2F;th&gt;&#xA;&lt;th&gt;Value&lt;&#x2F;th&gt;&#xA;&lt;th&gt;Hexadecimal&lt;&#x2F;th&gt;&#xA;&lt;th&gt;Description&lt;&#x2F;th&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;&#x2F;thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;hwnd&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;789538&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;0xc0c22&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;our window&#x27;s handle&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;msg&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;537&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;0x0219&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/devio/wm-devicechange#parameters&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;WM_DEVICECHANGE&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; message&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;wparam&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;32772&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;0x8004&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;a href=&#34;https://docs.microsoft.com/en-us/windows/win32/devio/dbt-deviceremovecomplete&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;DBT_DEVICEREMOVECOMPLETE&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; event&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;lparam&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;346174713248&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;0x50999eeda0&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;pointer (memory address) to event info&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;&#x2F;tbody&gt;&#xA;&lt;&#x2F;table&gt;&#xA;&lt;&#x2F;div&gt;&#xA;&lt;p&gt;Once we decipher the message type, we can google its hexadecimal value to find which message it corresponds to, then figure out what it contains. Here we don&#x27;t really need to dereference and unpack &lt;code&gt;lparam&lt;&#x2F;code&gt; pointer, because &lt;a href=&#34;#getting-a-list-of-drives&#34;&gt;we&#x27;re using WMI&lt;&#x2F;a&gt; for that. We just need to know something has happened.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&lt;code&gt;WM_DEVICECHANGE&lt;&#x2F;code&gt; message gives us &lt;code&gt;DBT_DEVICEARRIVAL&lt;&#x2F;code&gt; and &lt;code&gt;DBT_DEVICEREMOVECOMPLETE&lt;&#x2F;code&gt; events to notify when a device is added or removed respectively.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Now putting these all together:&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;python-script&#34;&gt;Python script&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;I wrapped the code I&#x27;ve explained above inside &lt;code&gt;DeviceListener&lt;&#x2F;code&gt; class. It provides an easy way to attach your own listener and perform a task when a drive is attached/removed.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;It assumes the script will be run as the main script, that&#x27;s why &lt;code&gt;DeviceListener.start()&lt;&#x2F;code&gt; is blocking. You can run it inside a thread if you want it to be non-blocking.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;You can also change the highlighted line to trigger the callback only when a drive is either inserted or removed.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;json&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;logging&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;subprocess&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;dataclasses&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;dataclass&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;typing&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32api&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;win32gui&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-n&#34;&gt;logging&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;basicConfig&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;level&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;logging&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;INFO&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;logging&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;getLogger&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-nd&#34;&gt;@dataclass&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;letter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;label&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;drive_type&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-nd&#34;&gt;@property&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;is_removable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;bool&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;drive_type&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Removable Disk&amp;#39;&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;DeviceListener&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    Listens to Win32 `WM_DEVICECHANGE` messages&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    and trigger a callback when a device has been plugged in or out&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    See: https://docs.microsoft.com/en-us/windows/win32/devio/wm-devicechange&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;WM_DEVICECHANGE_EVENTS&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mh&#34;&gt;0x0019&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_CONFIGCHANGECANCELED&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;A request to change the current configuration (dock or undock) has been canceled.&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mh&#34;&gt;0x0018&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_CONFIGCHANGED&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;The current configuration has changed, due to a dock or undock.&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mh&#34;&gt;0x8006&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_CUSTOMEVENT&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;A custom event has occurred.&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mh&#34;&gt;0x8000&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_DEVICEARRIVAL&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;A device or piece of media has been inserted and is now available.&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mh&#34;&gt;0x8001&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_DEVICEQUERYREMOVE&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Permission is requested to remove a device or piece of media. Any application can deny this request and cancel the removal.&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mh&#34;&gt;0x8002&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_DEVICEQUERYREMOVEFAILED&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;A request to remove a device or piece of media has been canceled.&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mh&#34;&gt;0x8004&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_DEVICEREMOVECOMPLETE&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;A device or piece of media has been removed.&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mh&#34;&gt;0x8003&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_DEVICEREMOVEPENDING&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;A device or piece of media is about to be removed. Cannot be denied.&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mh&#34;&gt;0x8005&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_DEVICETYPESPECIFIC&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;A device-specific event has occurred.&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mh&#34;&gt;0x0007&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_DEVNODES_CHANGED&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;A device has been added to or removed from the system.&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mh&#34;&gt;0x0017&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_QUERYCHANGECONFIG&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Permission is requested to change the current configuration (dock or undock).&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mh&#34;&gt;0xFFFF&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_USERDEFINED&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;The meaning of this message is user-defined.&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;),&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-fm&#34;&gt;__init__&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]],&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;_create_window&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        Create a window for listening to messages&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        https://docs.microsoft.com/en-us/windows/win32/learnwin32/creating-a-window#creating-the-window&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        See also: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindoww&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        :return: window hwnd&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        &amp;#34;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;WNDCLASS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;lpfnWndProc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_on_message&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;lpszClassName&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__class__&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hInstance&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32api&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetModuleHandle&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;class_atom&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;RegisterClass&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CreateWindow&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;class_atom&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__class__&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hInstance&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;start&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;info&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-sa&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Listening to drive changes&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;_create_window&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;debug&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-sa&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Created listener window with hwnd=&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;:&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;x&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;debug&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-sa&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Listening to messages&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;win32gui&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PumpMessages&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;_on_message&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;hwnd&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;msg&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wparam&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;lparam&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;msg&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;!=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;win32con&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;WM_DEVICECHANGE&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;event&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;description&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;WM_DEVICECHANGE_EVENTS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;wparam&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;debug&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-sa&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Received message: &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;event&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt; = &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;description&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;event&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_DEVICEREMOVECOMPLETE&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;DBT_DEVICEARRIVAL&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;info&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;A device has been plugged in (or out)&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;list_drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;())&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-nd&#34;&gt;@staticmethod&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;list_drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        Get a list of drives using WMI&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        :return: list of drives&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;        &amp;#34;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;run&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;powershell&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-noprofile&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-command&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;                &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Get-WmiObject -Class Win32_LogicalDisk | Select-Object deviceid,volumename,drivetype | ConvertTo-Json&amp;#39;&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;text&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PIPE&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;returncode&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;!=&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;or&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;not&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;strip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;error&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Failed to enumerate drives&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[]&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;devices&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;json&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;loads&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-n&#34;&gt;drive_types&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Unknown&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-mi&#34;&gt;1&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;No Root Directory&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-mi&#34;&gt;2&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Removable Disk&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-mi&#34;&gt;3&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Local Disk&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-mi&#34;&gt;4&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Network Drive&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-mi&#34;&gt;5&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Compact Disc&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-mi&#34;&gt;6&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;RAM Disk&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;letter&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;deviceid&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;label&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;volumename&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;drive_type&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;drive_types&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;drivetype&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]]&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;devices&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;on_devices_changed&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]):&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;removable_drives&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;drives&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;is_removable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;debug&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-sa&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Connected removable drives: &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;removable_drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;drive&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;removable_drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;backup&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;backup&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;label&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;!=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;ABDUS&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;info&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Backup drive has been plugged in&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;logger&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;info&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-sa&#34;&gt;f&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Backing up &lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;{&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;letter&lt;&#x2F;span&gt;&lt;span class=&#34;z-si&#34;&gt;}&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;listener&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;DeviceListener&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_devices_changed&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;listener&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;start&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;When we run it and plug in a drive, it logs:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;console&#34; data-highlighted=&#34;true&#34; class=&#34;language-console z-code z-chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;z-go&#34;&gt;INFO:__main__:Listening to drive changes&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;DEBUG:__main__:Created listener window with hwnd=d60b86&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;DEBUG:__main__:Listening to messages&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;DEBUG:__main__:Received message: DBT_DEVNODES_CHANGED = A device has been added to or removed from the system.&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;DEBUG:__main__:Received message: DBT_DEVNODES_CHANGED = A device has been added to or removed from the system.&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;DEBUG:__main__:Received message: DBT_DEVICEARRIVAL = A device or piece of media has been inserted and is now available.&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;INFO:__main__:A device has been plugged in (or out)&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;DEBUG:__main__:Connected removable drives: [Drive(letter=&amp;#39;E:&amp;#39;, label=&amp;#39;ABDUS&amp;#39;, drive_type=&amp;#39;Removable Disk&amp;#39;)]&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;INFO:__main__:Backup drive has been plugged in&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;INFO:__main__:Backing up E:&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;It works 🙌.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Now what you do when your callback is called is up to you. You can:&lt;&#x2F;p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Copy files to/from the drive&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Backup photos when an SD card is inserted&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Display a popup to warn the user&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Lock the PC&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Wipe the drive (🤷‍♂️ you do you)&lt;&#x2F;li&gt;&#xA;&lt;li&gt;...&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;&lt;h2 id=&#34;alternative-approach-polling-for-drives&#34;&gt;Alternative approach: polling for drives&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;This is my older attempt where I poll disks and trigger a callback when the set of drives change. It&#x27;s considerably simpler, doesn&#x27;t depend on &lt;a href=&#34;https://github.com/mhammond/pywin32&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;pywin32&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and easier to port to other operating systems (provided you know how to list drives on that platform).&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;json&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;subprocess&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;dataclasses&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;dataclass&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-kn&#34;&gt;from&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;typing&lt;&#x2F;span&gt; &lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-nd&#34;&gt;@dataclass&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;class&lt;&#x2F;span&gt; &lt;span class=&#34;z-nc&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;letter&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;label&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;drive_type&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;str&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-nd&#34;&gt;@property&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;is_removable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;bool&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-bp&#34;&gt;self&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;drive_type&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Removable Disk&amp;#39;&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;list_drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    Get a list of drives using WMI&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    :return: list of drives&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;run&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;args&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;powershell&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-noprofile&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;-command&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Get-WmiObject -Class Win32_LogicalDisk | Select-Object deviceid,volumename,drivetype | ConvertTo-Json&amp;#39;&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;text&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;subprocess&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;PIPE&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;returncode&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;!=&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;or&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;not&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;strip&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;():&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Failed to enumerate drives&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[]&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;devices&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;json&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;loads&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;proc&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;stdout&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-n&#34;&gt;drive_types&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Unknown&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;1&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;No Root Directory&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;2&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Removable Disk&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;3&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Local Disk&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;4&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Network Drive&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;5&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;Compact Disc&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-mi&#34;&gt;6&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;RAM Disk&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&#xA;    &lt;span class=&#34;z-k&#34;&gt;return&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;letter&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;deviceid&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;label&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;volumename&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;drive_type&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;drive_types&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;drivetype&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]]&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;d&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;devices&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;def&lt;&#x2F;span&gt; &lt;span class=&#34;z-nf&#34;&gt;watch_drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Callable&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;List&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Drive&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]],&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;],&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;poll_interval&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;int&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;1&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;):&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;prev&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;None&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-k&#34;&gt;while&lt;&#x2F;span&gt; &lt;span class=&#34;z-kc&#34;&gt;True&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;drives&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;list_drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;prev&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;!=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;            &lt;span class=&#34;z-n&#34;&gt;prev&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;drives&lt;&#x2F;span&gt;&#xA;        &lt;span class=&#34;z-n&#34;&gt;sleep&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;poll_interval&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-vm&#34;&gt;__name__&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;==&lt;&#x2F;span&gt; &lt;span class=&#34;z-s1&#34;&gt;&amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-n&#34;&gt;watch_drives&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;on_change&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;You can supply your own &lt;code&gt;on_change&lt;&#x2F;code&gt; callback and perform a job if a drive is present.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Here&#x27;s the output after I plug a USB drive in and out:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;console&#34; data-highlighted=&#34;true&#34; class=&#34;language-console z-code z-chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;z-go&#34;&gt;[Drive(letter=&amp;#39;C:&amp;#39;, label=&amp;#39;&amp;#39;, drive_type=&amp;#39;Local Disk&amp;#39;)]&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;[Drive(letter=&amp;#39;C:&amp;#39;, label=&amp;#39;&amp;#39;, drive_type=&amp;#39;Local Disk&amp;#39;), Drive(letter=&amp;#39;E:&amp;#39;, label=&amp;#39;ABDUS&amp;#39;, drive_type=&amp;#39;Removable Disk&amp;#39;)]&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;[Drive(letter=&amp;#39;C:&amp;#39;, label=&amp;#39;&amp;#39;, drive_type=&amp;#39;Local Disk&amp;#39;)]&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;That&#x27;s it.&lt;br /&gt;&#xA;Cheers ✌&lt;&#x2F;p&gt;&#xA;&lt;p&gt;If you&#x27;ve found this post useful, consider sharing it.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>Control Windows 10 native hotspot with PowerShell</title>
            <pubDate>Tue, 16 Jun 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/powershell-hotspot/</link>
            <guid>https://abdus.dev/posts/powershell-hotspot/</guid>
            <description>&lt;p&gt;There used to be a way to turn on or off the hotspot on Windows 7, but it doesn&#x27;t seem to work well with some wifi adapters&#xA;like Intel 9260 wifi card I&#x27;ve installed on my XPS 13. So I cannot control hotspot using &lt;code&gt;netsh&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-n&#34;&gt;PS&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;netsh&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;wlan&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;start &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hostednetwork&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-n&#34;&gt;The&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;hosted&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;network&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;couldn&lt;&#x2F;span&gt;&lt;span class=&#34;z-err&#34;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;t&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;be&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;started&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;The&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;group &lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;or&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;resource&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;is&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;not&lt;&#x2F;span&gt; &lt;span class=&#34;z-k&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;the&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;correct&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;state&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;to&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;perform&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;the&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;requested&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;operation&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;The only way to control native hotspot was so far using the UI&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/posts/powershell-hotspot/tray.png&#34; alt=&#34;&#34; /&gt;&lt;&#x2F;p&gt;&#xA;&lt;p&gt;As of Windows 10 v1607 there&#x27;s a &lt;a href=&#34;https://docs.microsoft.com/en-us/uwp/api/windows.networking.networkoperators.networkoperatortetheringmanager&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;new API&lt;&#x2F;a&gt; for UWP apps that we can use to start / stop hotspot tethering in PowerShell:&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Here&#x27;s the script based off of &lt;a href=&#34;https://stackoverflow.com/a/55563418/5298150&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;this answer&lt;&#x2F;a&gt; on StackOverflow:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-k&#34;&gt;param&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;string&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$state&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$connectionProfile&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Windows&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Networking&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Connectivity&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;NetworkInformation&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Windows&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Networking&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Connectivity&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ContentType&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;WindowsRuntime&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;GetInternetConnectionProfile&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$tetheringManager&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Windows&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Networking&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;NetworkOperators&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;NetworkOperatorTetheringManager&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;Windows&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Networking&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;NetworkOperators&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;ContentType&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;WindowsRuntime&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CreateFromConnectionProfile&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$connectionProfile&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-k&#34;&gt;if&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$state&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-eq&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;on&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nv&#34;&gt;$tetheringManager&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;StartTetheringAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;|&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;Out-Null&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;elseif&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$state&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;-eq&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;off&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nv&#34;&gt;$tetheringManager&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;StopTetheringAsync&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;|&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;Out-Null&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;else&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nv&#34;&gt;$tetheringManager&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;TetheringOperationalState&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Save this as &lt;code&gt;hotspot.ps1&lt;&#x2F;code&gt; and run it from PowerShell&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;.\&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hotspot&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;ps1&lt;&#x2F;span&gt; &lt;span class=&#34;z-c&#34;&gt;# get the current state (on/off)&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;.\&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hotspot&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;ps1&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;on&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;.\&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;hotspot&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;ps1&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;off&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;or call it from cmd:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;cmd&#34; data-highlighted=&#34;true&#34; class=&#34;language-cmd z-code z-chroma&#34;&gt;&lt;code class=&#34;language-cmd&#34; data-lang=&#34;cmd&#34;&gt;powershell -file hotspot.ps1 on &lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;</description>
        </item>
        <item>
            <title>Guide to editing file metadata using PowerShell</title>
            <pubDate>Mon, 25 May 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/powershell-file-metadata-guide/</link>
            <guid>https://abdus.dev/posts/powershell-file-metadata-guide/</guid>
            <description>&lt;div class=&#34;table-of-contents&#34;&gt;&#xA;    &lt;ul&gt;&#xA;        &lt;li style=&#34;margin-left: 0rem&#34;&gt;&lt;a href=&#34;#common-metadata&#34;&gt;Common metadata&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;        &lt;li style=&#34;margin-left: 1rem&#34;&gt;&lt;a href=&#34;#creation-and-modification-date&#34;&gt;Creation and modification date&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;        &lt;li style=&#34;margin-left: 0rem&#34;&gt;&lt;a href=&#34;#media-files&#34;&gt;Media files&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;        &lt;li style=&#34;margin-left: 1rem&#34;&gt;&lt;a href=&#34;#loading-taglib&#34;&gt;Loading TagLib#&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;        &lt;li style=&#34;margin-left: 1rem&#34;&gt;&lt;a href=&#34;#image-metadata&#34;&gt;Image metadata&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;        &lt;li style=&#34;margin-left: 1rem&#34;&gt;&lt;a href=&#34;#video-metadata&#34;&gt;Video metadata&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;        &lt;li style=&#34;margin-left: 1rem&#34;&gt;&lt;a href=&#34;#cover-photo&#34;&gt;Cover photo&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;    &lt;&#x2F;ul&gt;&#xA;&lt;&#x2F;div&gt;&#xA;&#xA;&lt;h2 id=&#34;common-metadata&#34;&gt;Common metadata&lt;&#x2F;h2&gt;&#xA;&lt;h3 id=&#34;creation-and-modification-date&#34;&gt;Creation and modification date&lt;&#x2F;h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Single file:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Get-ChildItem&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;.\&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;file&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;).&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;CreationTime&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;2026-10-07 12:00&amp;#34;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Get-ChildItem&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;.\&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;file&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;ext&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;).&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;LastWriteTime&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;2026-10-07 12:00&amp;#34;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;All files in current folder:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-nb&#34;&gt;Get-ChildItem&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;|&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;ForEach-Object&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$_&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;CreationTime&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;2026-10-07&amp;#34;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nb&#34;&gt;Get-ChildItem&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;|&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;ForEach-Object&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$_&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;LastWriteTime&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;2026-10-07&amp;#34;&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;&lt;h2 id=&#34;media-files&#34;&gt;Media files&lt;&#x2F;h2&gt;&#xA;&lt;h3 id=&#34;loading-taglib&#34;&gt;Loading TagLib#&lt;&#x2F;h3&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/mono/taglib-sharp&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;TagLib#&lt;&#x2F;a&gt; is a really great library crafted for this job. Download the &lt;a href=&#34;https://www.nuget.org/api/v2/package/TagLibSharp&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;NuGet package&lt;&#x2F;a&gt; and unzip it.&#xA;Copy the &lt;code&gt;TagLibSharp.dll&lt;&#x2F;code&gt; into where you&#x27;ll be running the script.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;shell&#34; data-highlighted=&#34;true&#34; class=&#34;language-shell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;.&#xA;├── lib&#xA;│   ├── net45&#xA;│   │   ├── ...&#xA;│   └── netstandard2.0&#xA;│       ├── TagLibSharp.dll &amp;lt;-- we need this one&#xA;│       ├── TagLibSharp.pdb&#xA;│       └── TaglibSharp.xml&#xA;├── ...&#xA;└── ...&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Load the DLL:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;System.Reflection.Assembly&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;LoadFrom&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;((&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Resolve-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;TagLibSharp.dll&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h3 id=&#34;image-metadata&#34;&gt;Image metadata&lt;&#x2F;h3&gt;&#xA;&lt;p&gt;Open the file and set properties in &lt;code&gt;.ImageTag&lt;&#x2F;code&gt; object and save it.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-nv&#34;&gt;$photo&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;TagLib.File&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Create&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;((&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Resolve-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;photo.jpg&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$photo&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;ImageTag&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Title&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;hello&amp;#34;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$photo&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;ImageTag&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Keywords&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;tag&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;another tag&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$photo&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;ImageTag&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Comment&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;my comment&amp;#34;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$photo&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;ImageTag&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;DateTime&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;System.DateTime&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Parse&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;2020-01-02&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$photo&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Save&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h3 id=&#34;video-metadata&#34;&gt;Video metadata&lt;&#x2F;h3&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/mono/taglib-sharp&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;TagLibSharp&lt;&#x2F;a&gt; can modify video metadata too.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-nv&#34;&gt;$video&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-nv&#34;&gt;$video&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;TagLib.File&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Create&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;((&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Resolve-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;./&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;mkv&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Tag&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Title&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;my video&amp;#34;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Tag&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Year&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-mf&#34;&gt;2020&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Tag&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Comment&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;a comment&amp;#34;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Tag&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Publisher&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;a publisher&amp;#34;&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Tag&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Description&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;a description&amp;#34;&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Save&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h3 id=&#34;cover-photo&#34;&gt;Cover photo&lt;&#x2F;h3&gt;&#xA;&lt;p&gt;Load the image file and override &lt;code&gt;.Tag.Pictures&lt;&#x2F;code&gt; with an array.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-nv&#34;&gt;$cover&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;TagLib.Picture&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;CreateFromPath&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;((&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Resolve-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;cover&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;png&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$video&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;TagLib.File&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Create&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;((&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Resolve-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;mp4&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Tag&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Pictures&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$cover&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$video&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Save&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$music&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-no&#34;&gt;TagLib.File&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;]::&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Create&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;((&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Resolve-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;music&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;mp3&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;))&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$music&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Tag&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Pictures&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nv&#34;&gt;$cover&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-nv&#34;&gt;$music&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-py&#34;&gt;Save&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;</description>
        </item>
        <item>
            <title>PowerShell aliases</title>
            <pubDate>Mon, 18 May 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/powershell-aliases/</link>
            <guid>https://abdus.dev/posts/powershell-aliases/</guid>
            <description>&lt;p&gt;A list of current aliases can be retrieved using &lt;a href=&#34;https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-alias&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;Get-Alias&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; command.&lt;&#x2F;p&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;Alias&lt;&#x2F;th&gt;&#xA;&lt;th&gt;Command&lt;&#x2F;th&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;&#x2F;thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;%&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;ForEach-Object&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;?&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Where-Object&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ac&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Add-Content&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;asnp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Add-PSSnapin&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;cat&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Content&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;cd&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-Location&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;CFS&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;ConvertFrom-String&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;chdir&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-Location&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;clc&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Clear-Content&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;clear&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Clear-Host&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;clhy&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Clear-History&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;cli&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Clear-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;clp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Clear-ItemProperty&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;cls&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Clear-Host&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;clv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Clear-Variable&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;cnsn&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Connect-PSSession&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;compare&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Compare-Object&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;copy&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Copy-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;cp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Copy-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;cpi&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Copy-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;cpp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Copy-ItemProperty&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;curl&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Invoke-WebRequest&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;cvpa&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Convert-Path&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;dbp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Disable-PSBreakpoint&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;del&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;diff&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Compare-Object&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;dir&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-ChildItem&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;dnsn&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Disconnect-PSSession&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ebp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Enable-PSBreakpoint&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;echo&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Write-Output&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;epal&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Export-Alias&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;epcsv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Export-Csv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;epsn&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Export-PSSession&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;erase&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;etsn&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Enter-PSSession&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;exsn&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Exit-PSSession&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;fc&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Format-Custom&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;fhx&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Format-Hex&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;fl&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Format-List&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;foreach&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;ForEach-Object&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ft&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Format-Table&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;fw&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Format-Wide&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gal&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Alias&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gbp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-PSBreakpoint&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gc&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Content&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gcb&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Clipboard&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gci&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-ChildItem&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gcm&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Command&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gcs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-PSCallStack&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gdr&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-PSDrive&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ghy&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-History&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gi&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gin&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-ComputerInfo&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gjb&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Job&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gl&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Location&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gm&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Member&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gmo&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Module&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-ItemProperty&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gps&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Process&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gpv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-ItemPropertyValue&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;group&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Group-Object&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gsn&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-PSSession&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gsnp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-PSSnapin&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gsv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Service&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gtz&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-TimeZone&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gu&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Unique&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Variable&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gwmi&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-WmiObject&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;h&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-History&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;history&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-History&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;icm&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Invoke-Command&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;iex&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Invoke-Expression&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ihy&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Invoke-History&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ii&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Invoke-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ipal&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Import-Alias&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ipcsv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Import-Csv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ipmo&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Import-Module&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ipsn&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Import-PSSession&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;irm&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Invoke-RestMethod&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ise&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;powershell_ise.exe&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;iwmi&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Invoke-WmiMethod&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;iwr&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Invoke-WebRequest&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;kill&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Stop-Process&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;lp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Out-Printer&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ls&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-ChildItem&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;man&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;help&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;md&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;mkdir&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;measure&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Measure-Object&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;mi&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Move-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;mount&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;New-PSDrive&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;move&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Move-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;mp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Move-ItemProperty&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;mv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Move-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;nal&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;New-Alias&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ndr&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;New-PSDrive&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ni&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;New-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;nmo&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;New-Module&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;npssc&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;New-PSSessionConfigurationFile&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;nsn&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;New-PSSession&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;nv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;New-Variable&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ogv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Out-GridView&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;oh&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Out-Host&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;popd&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Pop-Location&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ps&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Process&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;pushd&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Push-Location&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;pwd&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Location&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;r&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Invoke-History&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rbp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-PSBreakpoint&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rcjb&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Receive-Job&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rcsn&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Receive-PSSession&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rd&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rdr&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-PSDrive&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ren&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Rename-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;ri&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rjb&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-Job&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rm&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rmdir&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rmo&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-Module&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rni&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Rename-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rnp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Rename-ItemProperty&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-ItemProperty&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rsn&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-PSSession&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rsnp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-PSSnapin&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rujb&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Resume-Job&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-Variable&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rvpa&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Resolve-Path&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;rwmi&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Remove-WmiObject&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;sajb&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Start-Job&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;sal&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-Alias&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;saps&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Start-Process&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;sasv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Start-Service&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;sbp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-PSBreakpoint&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;sc&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-Content&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;scb&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-Clipboard&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;select&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Select-Object&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;set&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-Variable&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;shcm&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Show-Command&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;si&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-Item&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;sl&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-Location&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;sleep&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Start-Sleep&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;sls&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Select-String&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;sort&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Sort-Object&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;sp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-ItemProperty&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;spjb&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Stop-Job&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;spps&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Stop-Process&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;spsv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Stop-Service&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;start&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Start-Process&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;stz&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-TimeZone&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;sujb&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Suspend-Job&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;sv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-Variable&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;swmi&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Set-WmiInstance&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;tee&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Tee-Object&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;trcm&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Trace-Command&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;type&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Get-Content&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;wget&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Invoke-WebRequest&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;where&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Where-Object&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;wjb&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Wait-Job&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;write&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;td&gt;&lt;code&gt;Write-Output&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&#xA;&lt;&#x2F;tr&gt;&#xA;&lt;&#x2F;tbody&gt;&#xA;&lt;&#x2F;table&gt;&#xA;</description>
        </item>
        <item>
            <title>Upgrade Python SQLite driver to latest version on Windows</title>
            <pubDate>Thu, 20 Feb 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/sqlite-upgrade-windows/</link>
            <guid>https://abdus.dev/posts/sqlite-upgrade-windows/</guid>
            <description>&lt;p&gt;SQLite has an extension called &lt;a href=&#34;https://www.sqlite.org/json1.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;JSON1&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; that allows working with JSON.&#xA;It&#x27;s really useful for storing data that wouldn&#x27;t necessarily belong to a separate column, like metadata.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Unfortunately on Windows, SQLite driver for Python doesn&#x27;t come bundled with this extension.&#xA;We need to upgrade the driver to a newer version to get it.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;You can find the compiled binaries for Windows on &lt;a href=&#34;https://www.sqlite.org/download.html#win32&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;SQLite website&lt;&#x2F;a&gt;.&#xA;Here are the links, scraped directly from the page:&lt;&#x2F;p&gt;&#xA;&lt;div class=&#34;download&#34;&gt;&#xA;&lt;ul id=&#34;sqlite-downloads&#34;&gt;&#xA;  &lt;li&gt;Fetching the latest download URLs...&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;&lt;script&gt;&#xA;    (async () =&gt; {&#xA;        const list = document.getElementById(&#x27;sqlite-downloads&#x27;);&#xA;        try {&#xA;            const url = &#x27;https://www.sqlite.org/download.html#win32&#x27;;&#xA;            const response = await fetch(`https://cors.abdus.dev?url=${encodeURIComponent(url)}`);&#xA;            if (!response.ok) throw new Error(&#x27;Failed to fetch SQLite downloads&#x27;);&#xA;            const html = await response.text();&#xA;            const paths = [...new Set((html.match(/\d+\/[^\s&#34;&#x27;&lt;&gt;]+\.zip/g) || [])&#xA;                .filter(path =&gt; /dll-win/.test(path)))];&#xA;            if (!paths.length) throw new Error(&#x27;No SQLite DLL downloads found&#x27;);&#xA;            list.replaceChildren(...paths.map(path =&gt; {&#xA;                const item = document.createElement(&#x27;li&#x27;);&#xA;                const link = document.createElement(&#x27;a&#x27;);&#xA;                link.href = `https://www.sqlite.org/${path}`;&#xA;                link.textContent = link.href;&#xA;                item.append(link);&#xA;                return item;&#xA;            }));&#xA;        } catch (error) {&#xA;            list.firstElementChild.textContent = &#x27;Unable to fetch downloads. Please use the SQLite website link above.&#x27;;&#xA;        }&#xA;    })();&#xA;&lt;&#x2F;script&gt;&#xA;&lt;&#x2F;div&gt;&#xA;&lt;p&gt;Extract the ZIP file and place the DLL into &lt;code&gt;$PYTHON_DIR/DLLs&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;You can find the exact location using PowerShell:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;powershell&#34; data-highlighted=&#34;true&#34; class=&#34;language-powershell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span class=&#34;z-nb&#34;&gt;start &lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Join-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Split-Path&lt;&#x2F;span&gt; &lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-nb&#34;&gt;Get-Command&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;python&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;).&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;Path&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;dlls&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-c&#34;&gt;# C:\Python39\dlls&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;or use cmd directly:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;cmd&#34; data-highlighted=&#34;true&#34; class=&#34;language-cmd z-code z-chroma&#34;&gt;&lt;code class=&#34;language-cmd&#34; data-lang=&#34;cmd&#34;&gt;powershell -command &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;start (Join-Path (Split-Path (Get-Command python).Path) &amp;#34;&lt;&#x2F;span&gt;dlls&lt;span class=&#34;z-s2&#34;&gt;&amp;#34;)&amp;#34;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;h2 id=&#34;sqlite-compile-options&#34;&gt;SQLite compile options&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Run &lt;code&gt;python&lt;&#x2F;code&gt;, and fetch the list of compilation options to verify if it has succeeded:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;python&#34; data-highlighted=&#34;true&#34; class=&#34;language-python z-code z-chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;z-kn&#34;&gt;import&lt;&#x2F;span&gt; &lt;span class=&#34;z-nn&#34;&gt;sqlite3&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;=&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;sqlite3&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;connect&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;:memory:&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;execute&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-s1&#34;&gt;&amp;#39;pragma compile_options&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;)&lt;&#x2F;span&gt;&lt;span class=&#34;z-o&#34;&gt;.&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;fetchall&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;()&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-k&#34;&gt;for&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;o&lt;&#x2F;span&gt; &lt;span class=&#34;z-ow&#34;&gt;in&lt;&#x2F;span&gt; &lt;span class=&#34;z-n&#34;&gt;options&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-nb&#34;&gt;print&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;(&lt;&#x2F;span&gt;&lt;span class=&#34;z-n&#34;&gt;o&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&lt;span class=&#34;z-mi&#34;&gt;0&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;])&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;pre data-lang=&#34;console&#34; data-highlighted=&#34;true&#34; class=&#34;language-console z-code z-chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;z-go&#34;&gt;COMPILER=msvc-1500&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;ENABLE_BYTECODE_VTAB&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;ENABLE_COLUMN_METADATA&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;ENABLE_DBSTAT_VTAB&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;ENABLE_FTS3&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;ENABLE_FTS4&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;ENABLE_FTS5&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;ENABLE_GEOPOLY&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;ENABLE_JSON1 &amp;lt;-- IT WORKS! 🎉&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;ENABLE_RTREE&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;ENABLE_STMTVTAB&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;MAX_TRIGGER_DEPTH=100&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;TEMP_STORE=1&#xA;&lt;&#x2F;span&gt;&lt;span class=&#34;z-go&#34;&gt;THREADSAFE=1&#xA;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;Thanks to the new driver, we also have access to &lt;a href=&#34;https://www.sqlite.org/fts5.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;FTS5&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; extension to work with full text search indices.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>Prayer Times API</title>
            <pubDate>Mon, 20 Jan 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/prayertimes/</link>
            <guid>https://abdus.dev/projects/prayertimes/</guid>
            <description>&lt;p&gt;&lt;strong&gt;2026 EDIT&lt;&#x2F;strong&gt;: ⚠️ This API has been deprecated, please use &lt;a href=&#34;/projects/prayertimes-v2/&#34;&gt;the new version&lt;&#x2F;a&gt; instead.&lt;&#x2F;p&gt;&#xA;&lt;hr /&gt;&#xA;&lt;p&gt;This API lets you fetch daily prayer times from &lt;a href=&#34;https://namazvakitleri.diyanet.gov.tr/en-US&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Presidency of Religious Affairs of Turkey&lt;&#x2F;a&gt;&#xA;(Diyanet İşleri Başkanlığı) in a nice, clean format.&#xA;You can use it to find a location with available prayer times info,&#xA;then query the schedule of that month&#x27;s prayer times.&lt;&#x2F;p&gt;&#xA;&#xA;&lt;a class=&#34;repo-card&#34; href=&#34;https://git.abdus.dev/abdus/prayertimes&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&#xA;    &lt;span class=&#34;repo-card__name&#34;&gt;&#xA;        &lt;span class=&#34;repo-card__owner&#34;&gt;abdus&lt;&#x2F;span&gt;&#xA;        &lt;span aria-hidden=&#34;true&#34;&gt;/&lt;&#x2F;span&gt;&#xA;        &lt;strong&gt;prayertimes&lt;&#x2F;strong&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__meta&#34;&gt;&#xA;        &#xA;        &lt;span&gt;Python&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span aria-label=&#34;0 stars&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-star&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;        &lt;span aria-label=&#34;0 forks&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-fork&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;&lt;&#x2F;a&gt;&#xA;&#xA;&lt;p&gt;I&#x27;ve created this API to prepare for a web app I&#x27;ll be building soon.&#xA;I am planning to structure it as a progressive web app that can be easily installed&#xA;and even planning to add push notifications to let user know of upcoming prayers.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;Swagger documentation for the API is reachable at &lt;a href=&#34;https://prayertimes.api.abdus.dev/docs&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;prayertimes.api.abdus.dev/docs&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;&#x2F;h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Find a location first by narrowing down country &amp;gt; city &amp;gt; region to get a location id.&#xA;Or search by country / state / city name:&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ol&gt;&#xA;&lt;pre data-lang=&#34;bash&#34; data-highlighted=&#34;true&#34; class=&#34;language-bash z-code z-chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;curl &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;https://prayertimes.api.abdus.dev/api/diyanet/search?q=istanbul&amp;#34;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;You&#x27;ll get a list of locations:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-err&#34;&gt;...&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;id&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;9541&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;country&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;TÜRKİYE&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;city&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;İSTANBUL&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;region&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;İSTANBUL&amp;#34;&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;id&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-mi&#34;&gt;9547&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;country&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;TÜRKİYE&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;city&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;İSTANBUL&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;region&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;ŞİLE&amp;#34;&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;}&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;Using the location &lt;code&gt;id&lt;&#x2F;code&gt; fetch the schedule of prayer times&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ol&gt;&#xA;&lt;pre data-lang=&#34;bash&#34; data-highlighted=&#34;true&#34; class=&#34;language-bash z-code z-chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;curl &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;https://prayertimes.api.abdus.dev/api/diyanet/prayertimes?location_id=9541&amp;#34;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;you&#x27;ll get a json like this:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;json&#34; data-highlighted=&#34;true&#34; class=&#34;language-json z-code z-chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;z-p&#34;&gt;[&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;date&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;2020-01-21T00:00:00&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;fajr&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;06:48&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;sun&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;08:17&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;dhuhr&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;13:20&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;asr&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;15:50&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;maghrib&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;18:13&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;isha&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;19:37&amp;#34;&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;{&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;date&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;2020-01-22T00:00:00&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;fajr&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;06:47&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;sun&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;08:17&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;dhuhr&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;13:20&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;asr&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;15:51&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;maghrib&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;18:14&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;,&lt;&#x2F;span&gt;&#xA;    &lt;span class=&#34;z-nt&#34;&gt;&amp;#34;isha&amp;#34;&lt;&#x2F;span&gt;&lt;span class=&#34;z-p&#34;&gt;:&lt;&#x2F;span&gt; &lt;span class=&#34;z-s2&#34;&gt;&amp;#34;19:38&amp;#34;&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-p&#34;&gt;},&lt;&#x2F;span&gt;&#xA;  &lt;span class=&#34;z-err&#34;&gt;...&lt;&#x2F;span&gt;&#xA;&lt;span class=&#34;z-p&#34;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;</description>
        </item>
        <item>
            <title>Deutscher</title>
            <pubDate>Sat, 18 Jan 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/deutscher/</link>
            <guid>https://abdus.dev/projects/deutscher/</guid>
            <description>&lt;p&gt;Deutscher is a collection of APIs to fetch example sentences related to a phrase, and perform translations.&#xA;It uses following sources to find sentences:&lt;&#x2F;p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://duden.de&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Duden.de&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.dict.cc/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Dict.cc&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.deepl.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Linguee/DeepL&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://context.reverso.net/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Reverso Context&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;&lt;p&gt;The API is reachable at &lt;a href=&#34;https://deutscher.app.abdus.dev/docs&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;deutscher.app.abdus.dev/docs&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;todo&#34;&gt;Todo&lt;&#x2F;h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;A web app optimized for mobile in front to consume the API.&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Export to Anki with an option to generate proper HTML markup.&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;</description>
        </item>
        <item>
            <title>artscii</title>
            <pubDate>Thu, 16 Jan 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/projects/ascii/</link>
            <guid>https://abdus.dev/projects/ascii/</guid>
            <description>&lt;p&gt;This is a simple PWA app for &lt;a href=&#34;http://www.figlet.org/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;figlet&lt;&#x2F;a&gt; to generate ASCII word art with hundreds of fonts to choose from. It uses &lt;a href=&#34;https://github.com/patorjk/figlet.js&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;figlet.js&lt;&#x2F;a&gt; library to render ASCII text.&lt;&#x2F;p&gt;&#xA;&lt;image-gallery class=&#34;image-gallery&#34; role=&#34;region&#34; aria-label=&#34;Media gallery&#34;&gt;&#xA;    &lt;div class=&#34;image-gallery__track&#34; tabindex=&#34;0&#34; aria-label=&#34;Scroll to see more images and videos&#34;&gt;&#xA;        &lt;img src=&#34;./artscii.png&#34; alt=&#34;artscii app screenshot&#34; loading=&#34;lazy&#34;&gt;&#xA;    &lt;&#x2F;div&gt;&#xA;&lt;&#x2F;image-gallery&gt;&#xA;&#xA;&lt;p&gt;The app is reachable &lt;a href=&#34;https://artscii-abdusdev.vercel.app/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;here&lt;&#x2F;a&gt;. The source is available on:&lt;&#x2F;p&gt;&#xA;&#xA;&lt;a class=&#34;repo-card&#34; href=&#34;https://git.abdus.dev/abdus/ascii-pwa&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&#xA;    &lt;span class=&#34;repo-card__name&#34;&gt;&#xA;        &lt;span class=&#34;repo-card__owner&#34;&gt;abdus&lt;&#x2F;span&gt;&#xA;        &lt;span aria-hidden=&#34;true&#34;&gt;/&lt;&#x2F;span&gt;&#xA;        &lt;strong&gt;ascii-pwa&lt;&#x2F;strong&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;    &#xA;    &lt;span class=&#34;repo-card__meta&#34;&gt;&#xA;        &#xA;        &lt;span&gt;JavaScript&lt;&#x2F;span&gt;&#xA;        &#xA;        &lt;span aria-label=&#34;0 stars&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-star&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;        &lt;span aria-label=&#34;0 forks&#34;&gt;&#xA;            &lt;svg aria-hidden=&#34;true&#34; focusable=&#34;false&#34; width=&#34;1em&#34; height=&#34;1em&#34;&gt;&#xA;                &lt;use href=&#34;/assets/images/repo-icons.svg?h=817c5427d1ef8a5c1475#repo-fork&#34; /&gt;&#xA;            &lt;&#x2F;svg&gt;&#xA;            0&#xA;        &lt;&#x2F;span&gt;&#xA;    &lt;&#x2F;span&gt;&#xA;&lt;&#x2F;a&gt;&#xA;&#xA;</description>
        </item>
        <item>
            <title>Thinkpad P1 Gen 2 bootloop fix</title>
            <pubDate>Mon, 06 Jan 2020 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/thinkpad-p1-bootloop-fix/</link>
            <guid>https://abdus.dev/posts/thinkpad-p1-bootloop-fix/</guid>
            <description>&lt;p&gt;Lately I&#x27;ve been using a Lenovo Thinkpad P1 Gen 2, my first, well actually second, ThinkPad.&#xA;Today I&#x27;ve depleted the battery completely, then let it hibernate.&#xA;When I plugged the A/C adapter back and pressed the power button, it didn&#x27;t turn on,&#xA;but kept boot-looping.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;I&#x27;m documenting this fix in case someone else encounters the same issue.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;problem&#34;&gt;Problem&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Lenovo Thinkpad P1 Gen 2 doesn&#x27;t turn on. Keyboard lights up then it resets. Force resetting&#xA;doesn&#x27;t help.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;fix&#34;&gt;Fix&lt;&#x2F;h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Remove the A/C adapter.&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Locate the emergency reset hole (#1 on the image below). See the &lt;a href=&#34;https://pcsupport.lenovo.com/tr/en/products/laptops-and-netbooks/thinkpad-x-series-laptops/thinkpad-x1-extreme-gen-2/manuals/um922835&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;manual&lt;&#x2F;a&gt; under &lt;strong&gt;Meet Your Computer &amp;gt; Bottom&lt;&#x2F;strong&gt; heading for reference.&lt;br /&gt;&#xA;&lt;img src=&#34;/posts/thinkpad-p1-bootloop-fix/p1_bottom.jpg&#34; alt=&#34;bottom view of Lenovo P1&#34; /&gt;&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Find something pointy like a pin/toothpick/paperclip and stick it in the emergency reset hole under the body.&lt;&#x2F;li&gt;&#xA;&lt;li&gt;Turn the PC back on.&lt;&#x2F;li&gt;&#xA;&lt;&#x2F;ul&gt;&#xA;&lt;p&gt;I guess it was that easy,&#xA;but I admit that I got flashbacks from trying to fix my old Dell XPS 13.&#xA;Having a hardware reset button is cool.&lt;&#x2F;p&gt;&#xA;</description>
        </item>
        <item>
            <title>Youtube oEmbed support</title>
            <pubDate>Tue, 31 Dec 2019 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/youtube-oembed/</link>
            <guid>https://abdus.dev/posts/youtube-oembed/</guid>
            <description>&lt;p&gt;Youtube supports &lt;a href=&#34;https://oembed.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;oEmbed&lt;&#x2F;a&gt; format. This means we can easily fetch information about a video, such&#xA;as its thumbnail image, HTML for embedding, title etc. without having to use Youtube API or scraping&#xA;the Youtube page. You can reach oEmbed API at:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;url&#34; data-highlighted=&#34;true&#34; class=&#34;language-url z-code z-chroma&#34;&gt;&lt;code class=&#34;language-url&#34; data-lang=&#34;url&#34;&gt;https://www.youtube.com/oembed?url=YOUTUBE_URL&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;It returns a JSON filled with metadata. You can explore the oEmbed response yourself using the demo below.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;demo&#34;&gt;Demo&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Paste in a url and click fetch to inspect an oembed json for a Youtube video.&lt;&#x2F;p&gt;&#xA;&lt;form id=&#34;yt&#34; @submit.prevent=&#34;handleSubmit()&#34; x-data=&#34;app()&#34; x-cloak&gt;&#xA;&lt;p&gt;&#xA;    &lt;label for=&#34;url&#34;&gt;URL:&lt;&#x2F;label&gt;&#xA;    &lt;input id=&#34;url&#34; type=&#34;url&#34; x-model=&#34;url&#34; name=&#34;url&#34; placeholder=&#34;https://url/to/youtube/video&#34;&gt;&#xA;&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&#xA;    &lt;span&gt;oEmbed URL:&lt;&#x2F;span&gt;&#xA;    &lt;pre class=&#x27;snippet&#x27; data-lang=&#34;url&#34; x-text=&#34;url ? oembedUrl(url) : &#x27;...&#x27;&#34;&gt;&lt;&#x2F;pre&gt;&#xA;&lt;&#x2F;p&gt;&#xA;&lt;p&gt;&#xA;    &lt;button type=&#34;submit&#34; class=&#34;button&#34;&gt;fetch!&lt;&#x2F;button&gt;&#xA;&lt;&#x2F;p&gt;&#xA;&lt;pre class=&#34;snippet&#34; data-lang=&#34;json&#34;&gt;&lt;code x-text=&#34;text&#34;&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;&#x2F;form&gt;&#xA;&lt;script src=&#34;/assets/js/alpine-3.17.4.min.js&#34; defer&gt;&lt;&#x2F;script&gt;&#xA;&lt;script&gt;&#xA;    function app() {&#xA;        return {&#xA;            url: &#x27;https://www.youtube.com/watch?v=dQw4w9WgXcQ&#x27;,&#xA;            text: &#x27;// json will appear here&#x27;,&#xA;            async handleSubmit() {&#xA;                if (!this.url) return;&#xA;                this.text = &#x27;fetching json...&#x27;;&#xA;                // https://www.youtube.com/oembed doesn&#x27;t send CORS headers, so I have to use a proxy here&#xA;                const url = `https://cors.abdus.dev/?url=${encodeURIComponent(this.oembedUrl(this.url))}`;&#xA;                const json = await fetch(url).then(r =&gt; r.json());&#xA;                this.text = JSON.stringify(json, null, 2);&#xA;            },&#xA;            oembedUrl(url) {&#xA;                return `https://www.youtube.com/oembed?url=${encodeURIComponent(url)}`&#xA;            }&#xA;        };&#xA;    }&#xA;&lt;&#x2F;script&gt;&#xA;&lt;style&gt;&#xA;    [x-cloak] {&#xA;        display: none;&#xA;    }&#xA;&lt;&#x2F;style&gt;&#xA;</description>
        </item>
        <item>
            <title>SSH Agent</title>
            <pubDate>Sat, 28 Dec 2019 00:00:00 +0000</pubDate>
            <author>Abdussamet Kocak</author>
            <link>https://abdus.dev/posts/ssh-agent/</link>
            <guid>https://abdus.dev/posts/ssh-agent/</guid>
            <description>&lt;p&gt;I have several servers that I use to host my projects. Sometimes I need to move data between the&#xA;servers, but realizing a server is missing an SSH key to another is annoying. This is where SSH&#xA;agent comes into play. It holds a keychain for SSH keys you choose and forwards them as you ssh&#xA;between the servers. No more using &lt;code&gt;ssh-copy-id&lt;&#x2F;code&gt; or manually editing &lt;code&gt;~/.ssh/authorized_keys&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;&#xA;&lt;p&gt;I&#x27;m also fan of &lt;a href=&#34;https://fishshell.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;fish shell&lt;&#x2F;a&gt;. It&#x27;s quite handy, but poses a difficulty when using snippets&#xA;from the internet.&lt;&#x2F;p&gt;&#xA;&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;&#x2F;h2&gt;&#xA;&lt;p&gt;Running &lt;code&gt;ssh-agent&lt;&#x2F;code&gt; sets a few environment variables. &lt;code&gt;ssh-add&lt;&#x2F;code&gt; adds default SSH key to keychain&#xA;. &lt;code&gt;eval&lt;&#x2F;code&gt; is for running the commands manually in fish shell that would work natively in&#xA;bash.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;bash&#34; data-highlighted=&#34;true&#34; class=&#34;language-bash z-code z-chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;z-nb&#34;&gt;eval&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;(&lt;&#x2F;span&gt;ssh-agent -c&lt;span class=&#34;z-o&#34;&gt;)&lt;&#x2F;span&gt;&#xA;ssh-add&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;running these gives:&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;bash&#34; data-highlighted=&#34;true&#34; class=&#34;language-bash z-code z-chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;abdus@home ~&amp;gt; &lt;span class=&#34;z-nb&#34;&gt;eval&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;(&lt;&#x2F;span&gt;ssh-agent -c&lt;span class=&#34;z-o&#34;&gt;)&lt;&#x2F;span&gt;&#xA;Agent pid &lt;span class=&#34;z-m&#34;&gt;7282&lt;&#x2F;span&gt;&#xA;abdus@home ~&amp;gt; ssh-add&#xA;Identity added: /home/abdus/.ssh/id_rsa &lt;span class=&#34;z-o&#34;&gt;(&lt;&#x2F;span&gt;/home/abdus/.ssh/id_rsa&lt;span class=&#34;z-o&#34;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;&lt;p&gt;After setting up the agent, we can connect to a server from a different server without setting up&#xA;SSH keys first.&lt;&#x2F;p&gt;&#xA;&lt;pre data-lang=&#34;shell&#34; data-highlighted=&#34;true&#34; class=&#34;language-shell z-code z-chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;abdus@home ~&amp;gt; &lt;span class=&#34;z-nb&#34;&gt;eval&lt;&#x2F;span&gt; &lt;span class=&#34;z-o&#34;&gt;(&lt;&#x2F;span&gt;ssh-agent -c&lt;span class=&#34;z-o&#34;&gt;)&lt;&#x2F;span&gt;&#xA;abdus@home ~&amp;gt; ssh-add&#xA;abdus@home ~&amp;gt; ssh abdus@server1&#xA;abdus@server1 ~&amp;gt; ssh abdus@server2&#xA;abdus@server2 ~&amp;gt; &lt;span class=&#34;z-c1&#34;&gt;# it works!&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&#xA;</description>
        </item>
    </channel>
</rss>
