<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jung Hyun, Nam</title>
    <description>The latest articles on DEV Community by Jung Hyun, Nam (@rkttu).</description>
    <link>https://dev.to/rkttu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F82693%2F3066a6a6-2539-48a5-8e61-d698a22dcd9b.png</url>
      <title>DEV Community: Jung Hyun, Nam</title>
      <link>https://dev.to/rkttu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rkttu"/>
    <language>en</language>
    <item>
      <title>Speech, search, and Stable Diffusion — calling HuggingFace from C#</title>
      <dc:creator>Jung Hyun, Nam</dc:creator>
      <pubDate>Mon, 11 May 2026 12:16:30 +0000</pubDate>
      <link>https://dev.to/rkttu/speech-search-and-stable-diffusion-calling-huggingface-from-c-2bkk</link>
      <guid>https://dev.to/rkttu/speech-search-and-stable-diffusion-calling-huggingface-from-c-2bkk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR.&lt;/strong&gt; I wrote &lt;a href="https://github.com/rkttu/dotnetpy" rel="noopener noreferrer"&gt;DotNetPy&lt;/a&gt;, a small C# library that calls into CPython via the C API.&lt;br&gt;
Version 0.6.0 ships three working samples — semantic search with &lt;code&gt;sentence-transformers&lt;/code&gt;, speech-to-text with Whisper, and text-to-image with Stable Diffusion Turbo — and a verification matrix that runs them under classic CPython 3.13 &lt;strong&gt;and&lt;/strong&gt; the new free-threaded builds (3.13t / 3.14t).&lt;br&gt;
The whole thing is Native AOT-compatible, and the per-call isolation that PEP 703 needs is exposed as a one-liner: &lt;code&gt;Python.CreateIsolated()&lt;/code&gt;.&lt;br&gt;
Code: &lt;a href="https://github.com/rkttu/dotnetpy" rel="noopener noreferrer"&gt;https://github.com/rkttu/dotnetpy&lt;/a&gt;.&lt;br&gt;
NuGet: &lt;code&gt;dotnet add package DotNetPy&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every few months I run into the same pattern: I need a HuggingFace model — Whisper for transcripts, a sentence-transformer for retrieval, sometimes Stable Diffusion — and I'm working in C#.&lt;/p&gt;

&lt;p&gt;The usual escape hatches all have downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Convert to ONNX.&lt;/strong&gt; Works for many vision/encoder models. Doesn't work for newer architectures, doesn't work for diffusion pipelines without a lot of effort, and the conversion itself is a separate project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stand up a Python micro-service.&lt;/strong&gt; Now you've got two processes, two deployment stories, and a network hop in your hot path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Call an external API.&lt;/strong&gt; Costs money, requires internet, and your data leaves the box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;a href="https://github.com/pythonnet/pythonnet" rel="noopener noreferrer"&gt;pythonnet&lt;/a&gt; or &lt;a href="https://github.com/tonybaloney/csnakes" rel="noopener noreferrer"&gt;CSnakes&lt;/a&gt;.&lt;/strong&gt; Solid choices. But pythonnet doesn't currently support Native AOT, and CSnakes pushes you into a Source Generator workflow. Neither has a public story for the free-threaded CPython builds yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted a thinner option: write Python inline as a string from C#, pass arrays in, get JSON-shaped results back, and have the whole thing AOT-compile to a single binary. That's what DotNetPy is. The three samples below all run end-to-end on a Windows 11 laptop with no GPU.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample 1 — Semantic search with &lt;code&gt;sentence-transformers&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The first sample encodes a small corpus, embeds a query, and returns the top-K most similar sentences as a &lt;code&gt;DotNetPyValue&lt;/code&gt; — a JSON-document wrapper that gets you back into the .NET world with &lt;code&gt;GetString()&lt;/code&gt;, &lt;code&gt;GetInt32()&lt;/code&gt;, &lt;code&gt;GetDouble()&lt;/code&gt;, and path-based property access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;DotNetPy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;DotNetPy.Uv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PythonProject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithProjectName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dotnetpy-ml-embeddings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithPythonVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"==3.12.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDependencies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"sentence-transformers==2.7.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"transformers==4.40.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"torch&amp;gt;=2.2,&amp;lt;2.5"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InitializeAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetExecutor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"
import numpy as np
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')
"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;corpus&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"Python is a popular programming language for data science."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"C# and .NET are great for building enterprise applications."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Rust offers memory safety without garbage collection."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Pizza is delicious with various toppings."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// …&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Tell me about programming languages"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAndCapture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"
corpus_emb = model.encode(corpus, normalize_embeddings=True)
query_emb = model.encode([query], normalize_embeddings=True)[0]
sims = corpus_emb @ query_emb
top_idx = np.argsort(-sims)[:3]
result = [
    {'rank': int(rank + 1), 'score': float(sims[i]), 'text': corpus[int(i)]}
    for rank, i in enumerate(top_idx)
]
"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"corpus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corpus&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;RootElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnumerateArray&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rank"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetInt32&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                      &lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetDouble&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="n"&gt;F3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                      &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actual output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  1. [0.578] Python is a popular programming language for data science.
  2. [0.370] C# and .NET are great for building enterprise applications.
  3. [0.203] Rust offers memory safety without garbage collection.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting bit is the boundary: &lt;code&gt;corpus&lt;/code&gt; is a .NET &lt;code&gt;string[]&lt;/code&gt; and &lt;code&gt;query&lt;/code&gt; is a .NET &lt;code&gt;string&lt;/code&gt;, both arriving in Python as native lists/strings. The scored results come back as a JSON document that the .NET side reads with the usual &lt;code&gt;JsonElement&lt;/code&gt; API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample 2 — Speech-to-text with Whisper
&lt;/h2&gt;

&lt;p&gt;Same shape, different modality. Drop a &lt;code&gt;.wav&lt;/code&gt;/&lt;code&gt;.flac&lt;/code&gt; file in, get back text plus chunk-level timestamps. The audio bytes never cross the .NET ↔ Python boundary — Python opens the file directly, the boundary only carries the structured transcript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetExecutor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"
from transformers import pipeline
import torch
asr = pipeline(
    'automatic-speech-recognition',
    model='openai/whisper-base.en',
    chunk_length_s=30,
    return_timestamps=True,
    torch_dtype=torch.float32,
)
"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAndCapture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"
out = asr(audio_path)
chunks = [
    {'start': float(c['timestamp'][0]), 'end': float(c['timestamp'][1]),
     'text': c['text'].strip()}
    for c in out.get('chunks', [])
    if c['timestamp'][0] is not None and c['timestamp'][1] is not None
]
result = {'text': out['text'].strip(), 'chunks': chunks}
"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"audio_path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;audioPath&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"\"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;\""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RootElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"chunks"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;EnumerateArray&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"  [&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetDouble&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="n"&gt;F2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s → "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                      &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetDouble&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="n"&gt;F2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s] "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                      &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run against a public-domain JFK clip that ships with the sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"And so my fellow Americans, ask not what your country can do for you,
 ask what you can do for your country."

  [0.00s → 11.00s] And so my fellow Americans, ask not what your country can
                   do for you, ask what you can do for your country.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;whisper-base.en&lt;/code&gt; is 290 MB, transcription of an 11-second clip takes about 7 seconds on CPU on my machine. Subsequent runs reuse the cached model and the venv — the only first-run cost is the initial download.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample 3 — Text-to-image with Stable Diffusion Turbo
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;stabilityai/sd-turbo&lt;/code&gt; is a one-step diffusion model. On CPU you get a 512×512 image in ~30 seconds; on a recent GPU you get one in ~2 seconds. The .NET side never sees the image bytes — Python writes the PNG to disk and hands back metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"
import torch
from diffusers import AutoPipelineForText2Image
pipe = AutoPipelineForText2Image.from_pretrained(
    'stabilityai/sd-turbo',
    torch_dtype=torch.float32,
    safety_checker=None, requires_safety_checker=False,
)
pipe.set_progress_bar_config(disable=True)
"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAndCapture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"
import time, os
t0 = time.time()
img = pipe(prompt=prompt, num_inference_steps=1, guidance_scale=0.0).images[0]
elapsed = time.time() - t0
out_path = os.path.join(out_dir, 'generated.png')
img.save(out_path)
result = {
    'path': out_path,
    'width': img.size[0],
    'height': img.size[1],
    'size_bytes': os.path.getsize(out_path),
    'elapsed_seconds': elapsed,
}
"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"a serene mountain lake at sunset, oil painting style"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"out_dir"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outDir&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"  Saved:   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"  Size:    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;×&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt; px, "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                  &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"size_bytes"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="n"&gt;N0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"  Inference: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"elapsed_seconds"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="n"&gt;F2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Saved:   .../samples/ml-image-gen/output/generated.png
  Size:    512×512 px, 434,242 bytes
  Inference: 31.19s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the pattern I want to highlight: &lt;strong&gt;only structured data crosses the boundary&lt;/strong&gt;. The PNG bytes (~400 KB), the embedding matrices, the float32 tensors — they all stay Python-side. The .NET side sees prompts going in and small JSON objects coming out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install + first run
&lt;/h2&gt;

&lt;p&gt;The library is just a NuGet package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package DotNetPy &lt;span class="nt"&gt;--version&lt;/span&gt; 0.6.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to follow the samples literally, &lt;a href="https://github.com/rkttu/dotnetpy/tree/main/samples" rel="noopener noreferrer"&gt;&lt;code&gt;samples/&lt;/code&gt;&lt;/a&gt; in the repo has the three above plus a &lt;a href="https://github.com/rkttu/dotnetpy/tree/main/samples/native-aot" rel="noopener noreferrer"&gt;&lt;code&gt;native-aot&lt;/code&gt;&lt;/a&gt; consumer that drives the AOT-published native DLL through its C exports — the path for embedding DotNetPy into C/C++/Rust hosts.&lt;/p&gt;

&lt;p&gt;The ML samples use &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt; to provision Python + the HuggingFace stack declaratively from C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PythonProject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithProjectName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-app"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithPythonVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"==3.12.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDependencies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"transformers==4.40.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"torch&amp;gt;=2.2,&amp;lt;2.5"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InitializeAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's enough to get a working &lt;code&gt;executor&lt;/code&gt;. No separate Python install, no manual venv juggling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I actually care about: PEP 703 free-threaded Python
&lt;/h2&gt;

&lt;p&gt;The interesting cliff for an interop library lands in 2025–26. CPython 3.13 introduced &lt;strong&gt;free-threaded builds&lt;/strong&gt; (the &lt;code&gt;t&lt;/code&gt; suffix: &lt;code&gt;python3.13t&lt;/code&gt;). The GIL goes away, and concurrent threads can really run Python code in parallel for the first time. This is fantastic for ML serving — you want multiple inference workers sharing one process — and it breaks a lot of implicit invariants in libraries written against the classic GIL.&lt;/p&gt;

&lt;p&gt;Specifically, pythonnet has been working through this. &lt;a href="https://github.com/pythonnet/pythonnet/pull/2721" rel="noopener noreferrer"&gt;PR #2721&lt;/a&gt; catalogues five categories of work needed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Refcount layout changes (&lt;code&gt;ob_refcnt&lt;/code&gt; is now a split structure)&lt;/li&gt;
&lt;li&gt;Concurrent type/object cache races&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Reflection.Emit&lt;/code&gt; thread safety&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GCHandle&lt;/code&gt; slot ownership atomicity&lt;/li&gt;
&lt;li&gt;Finalizer / &lt;code&gt;Py_Finalize&lt;/code&gt; race&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When DotNetPy 0.6.0 happened, I used pythonnet's PR as the audit lens. &lt;strong&gt;Four of those five categories don't apply to DotNetPy by design&lt;/strong&gt; — it doesn't bridge .NET and Python type systems, doesn't subclass Python types from CLR, doesn't use &lt;code&gt;Reflection.Emit&lt;/code&gt;, doesn't expose &lt;code&gt;GCHandle&lt;/code&gt; slots to Python, and doesn't call &lt;code&gt;Py_Finalize&lt;/code&gt;. The fifth (finalizer / shutdown) was mitigated with an explicit &lt;code&gt;PyGILState_Ensure&lt;/code&gt; guard around &lt;code&gt;Py_DecRef&lt;/code&gt; in &lt;code&gt;SafeHandle.ReleaseHandle&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What did surface from the audit, and got fixed in 0.6.0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Internal scratch names in shared &lt;code&gt;__main__&lt;/code&gt; globals.&lt;/strong&gt; Every helper variable (&lt;code&gt;_json_result&lt;/code&gt;, &lt;code&gt;_is_valid&lt;/code&gt;, …) is now minted per-call via &lt;code&gt;Interlocked.Increment&lt;/code&gt;, so two concurrent callers don't race on the same slot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Evaluate&lt;/code&gt; leaking a shared &lt;code&gt;result&lt;/code&gt; global.&lt;/strong&gt; Same fix — per-call unique sink, cleaned up in &lt;code&gt;finally&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The two &lt;code&gt;__main__&lt;/code&gt;-globals fixes interact in a subtle way: even after they shipped, the existing user-variable injection (the &lt;code&gt;variables:&lt;/code&gt; parameter on &lt;code&gt;Execute&lt;/code&gt; / &lt;code&gt;ExecuteAndCapture&lt;/code&gt;) still wrote into shared &lt;code&gt;__main__&lt;/code&gt; globals. Two concurrent callers using the same user name would still collide.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix for that — and the most user-visible 0.6.0 addition — is a factory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;iso&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateIsolated&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"import json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data = {'k': 1}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// only this executor sees `data`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CreateIsolated()&lt;/code&gt; produces an executor that owns its own Python dict, pre-populated with &lt;code&gt;__builtins__&lt;/code&gt;. Each isolated executor coexists with the shared singleton and with other isolated executors; nothing leaks between them.&lt;/p&gt;

&lt;p&gt;That makes the concurrent ML pattern obvious:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;For&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessorCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threadId&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;iso&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateIsolated&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"import torch; from transformers import pipeline"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"
asr = pipeline('automatic-speech-recognition',
               model='openai/whisper-base.en')
"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAndCapture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"
out = asr(audio_path)
result = {'text': out['text']}
"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"audio_path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a free-threaded CPython build, that loop runs &lt;strong&gt;truly in parallel&lt;/strong&gt; — every worker has its own &lt;code&gt;asr&lt;/code&gt; pipeline and its own Python namespace. On the classic GIL build the same code is correct but serializes at the interpreter (and you'd hit the same wall regardless of which interop library you used).&lt;/p&gt;

&lt;p&gt;I verified the matrix on three builds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Python build&lt;/th&gt;
&lt;th&gt;Unit tests&lt;/th&gt;
&lt;th&gt;Native AOT consumer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPython 3.13 (GIL, auto-discovered)&lt;/td&gt;
&lt;td&gt;209 / 1 / &lt;strong&gt;0&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;8 / 8 ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPython 3.13.13t (free-threaded)&lt;/td&gt;
&lt;td&gt;205 / 5 / &lt;strong&gt;0&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;8 / 8 ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPython 3.14.4t (free-threaded)&lt;/td&gt;
&lt;td&gt;205 / 5 / &lt;strong&gt;0&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;8 / 8 ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full audit lives in &lt;a href="https://github.com/rkttu/dotnetpy/blob/main/docs/FREETHREADED-AUDIT.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/FREETHREADED-AUDIT.md&lt;/code&gt;&lt;/a&gt;. It's deliberately a public document — when I claim "verified", you can read what that means.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats, honestly
&lt;/h2&gt;

&lt;p&gt;A few things to be straight about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DotNetPy is 0.6.0.&lt;/strong&gt; Experimental, not production-stable yet. Lots of patterns are still being worked out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Python ML stack itself isn't fully free-threaded yet.&lt;/strong&gt; Torch's FT support is in active migration. NumPy 2.1+ supports PEP 703. &lt;code&gt;transformers&lt;/code&gt; and &lt;code&gt;diffusers&lt;/code&gt; work, but their underlying C extensions are mixed. Until the upstream stack catches up, you'll get correctness under free-threaded Python from DotNetPy's interop layer but Python-side ML performance may still serialize through library locks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native AOT publishing requires the platform C toolchain.&lt;/strong&gt; On Windows that means the Visual Studio C++ build tools; on Linux you need &lt;code&gt;clang&lt;/code&gt;/&lt;code&gt;lld&lt;/code&gt;. Same constraint as any AOT'd .NET app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON marshalling is the data plane.&lt;/strong&gt; Every result variable is serialized in Python and deserialized in .NET via &lt;code&gt;System.Text.Json&lt;/code&gt;. This is a deliberate trade-off for Native AOT compatibility. For workloads where this dominates (very large result objects), batch results into a single capture call and only return the small structured summary.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code&lt;/strong&gt;: &lt;a href="https://github.com/rkttu/dotnetpy" rel="noopener noreferrer"&gt;https://github.com/rkttu/dotnetpy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NuGet&lt;/strong&gt;: &lt;code&gt;dotnet add package DotNetPy --version 0.6.0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Samples&lt;/strong&gt;: &lt;a href="https://github.com/rkttu/dotnetpy/tree/main/samples/ml-embeddings" rel="noopener noreferrer"&gt;&lt;code&gt;samples/ml-embeddings&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/rkttu/dotnetpy/tree/main/samples/ml-whisper" rel="noopener noreferrer"&gt;&lt;code&gt;samples/ml-whisper&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/rkttu/dotnetpy/tree/main/samples/ml-image-gen" rel="noopener noreferrer"&gt;&lt;code&gt;samples/ml-image-gen&lt;/code&gt;&lt;/a&gt; — each is a single &lt;code&gt;dotnet run sample.cs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free-threaded audit&lt;/strong&gt;: &lt;a href="https://github.com/rkttu/dotnetpy/blob/main/docs/FREETHREADED-AUDIT.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/FREETHREADED-AUDIT.md&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparison with pythonnet / CSnakes / IronPython&lt;/strong&gt;: &lt;a href="https://github.com/rkttu/dotnetpy/blob/main/docs/COMPARISON.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/COMPARISON.md&lt;/code&gt;&lt;/a&gt; with a decision tree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've been wondering "how do I run a current HuggingFace model from C#" — I hope this is a useful answer. Comments, issues, and PRs welcome.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>What if calling Python from C# took 4 lines, supported Native AOT, and didn't need a project file?
Just shipped DotNetPy v0.5.0 — a lightweight Python interop library built for modern .NET.
📦 https://github.com/rkttu/dotnetpy</title>
      <dc:creator>Jung Hyun, Nam</dc:creator>
      <pubDate>Thu, 19 Mar 2026 07:01:43 +0000</pubDate>
      <link>https://dev.to/rkttu/what-if-calling-python-from-c-took-4-lines-supported-native-aot-and-didnt-need-a-project-file-2na8</link>
      <guid>https://dev.to/rkttu/what-if-calling-python-from-c-took-4-lines-supported-native-aot-and-didnt-need-a-project-file-2na8</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://github.com/rkttu/dotnetpy" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopengraph.githubassets.com%2F0799907337fbe66fd58f152aa4ef12ac9890e0486c9017b1461e180f4474e316%2Frkttu%2Fdotnetpy" height="600" class="m-0" width="1200"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://github.com/rkttu/dotnetpy" rel="noopener noreferrer" class="c-link"&gt;
            GitHub - rkttu/dotnetpy: Lightweight and AOT-compatible Python Interop Library · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Lightweight and AOT-compatible Python Interop Library - rkttu/dotnetpy
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg" width="32" height="32"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Introducing DotNetPy: Python Interop for Modern .NET, Reimagined</title>
      <dc:creator>Jung Hyun, Nam</dc:creator>
      <pubDate>Thu, 19 Mar 2026 06:58:50 +0000</pubDate>
      <link>https://dev.to/rkttu/introducing-dotnetpy-python-interop-for-modern-net-reimagined-32gi</link>
      <guid>https://dev.to/rkttu/introducing-dotnetpy-python-interop-for-modern-net-reimagined-32gi</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;.NET and Python are two of the most widely used platforms in enterprise software today, yet bringing them together has always been painful. Existing interop libraries either require heavy runtime dependencies, lack support for modern .NET features like Native AOT, or demand complex project configurations with Source Generators.&lt;/p&gt;

&lt;p&gt;If you've ever wanted to leverage Python's rich data science and ML ecosystem from a C# application — without leaving your .NET toolchain — you know the friction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DotNetPy&lt;/strong&gt; is my answer to that problem. Version 0.5.0 is now available on NuGet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is DotNetPy?
&lt;/h2&gt;

&lt;p&gt;DotNetPy (pronounced &lt;em&gt;dot-net-pie&lt;/em&gt;) is a .NET library for executing Python code directly from C#. It wraps the Python C API behind a clean, minimal interface.&lt;/p&gt;

&lt;p&gt;Here's the entire "hello world":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;temperatures&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;23.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;19.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;31.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;27.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;22.1&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAndCapture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"
    import statistics
    result = {'mean': statistics.mean(data), 'stdev': statistics.stdev(data)}
"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperatures&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Mean: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;GetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mean"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="n"&gt;F1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;°C ± &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;GetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"stdev"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="n"&gt;F1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No separate &lt;code&gt;.py&lt;/code&gt; files. No Source Generators. No complex setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Another Interop Library?
&lt;/h2&gt;

&lt;p&gt;There are established options — pythonnet, CSnakes, IronPython. Each has strengths, but none was designed for where .NET is heading in 2025 and beyond. DotNetPy fills that gap with three design pillars:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Native AOT Support
&lt;/h3&gt;

&lt;p&gt;DotNetPy is the &lt;strong&gt;only&lt;/strong&gt; .NET-Python interop library that works with &lt;code&gt;PublishAot=true&lt;/code&gt;. If you're building self-contained, ahead-of-time compiled applications, DotNetPy won't hold you back. Neither pythonnet nor CSnakes support this today.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. File-based App Ready (.NET 10+)
&lt;/h3&gt;

&lt;p&gt;.NET 10 introduces file-based apps — run a single &lt;code&gt;.cs&lt;/code&gt; file with &lt;code&gt;dotnet run script.cs&lt;/code&gt;, no project file required. DotNetPy is designed to work in this scenario from day one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# No csproj needed&lt;/span&gt;
dotnet run my-analysis.cs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it ideal for scripting, prototyping, and lightweight automation tasks where spinning up a full project is overkill.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Declarative uv Integration
&lt;/h3&gt;

&lt;p&gt;Managing Python environments from .NET has always been a manual, error-prone process. DotNetPy integrates with &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;uv&lt;/a&gt;, the fast Python package manager, so you can declare your entire Python environment in C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PythonProject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithProjectName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-analysis"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithPythonVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;=3.10"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDependencies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"numpy&amp;gt;=1.24.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pandas&amp;gt;=2.0.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InitializeAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// Downloads Python, creates venv, installs packages&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetExecutor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"import numpy as np; print(np.mean([1,2,3]))"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One call to &lt;code&gt;InitializeAsync()&lt;/code&gt; handles Python download, virtual environment creation, and dependency installation. No more "works on my machine" issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does It Compare?
&lt;/h2&gt;

&lt;p&gt;Here's a quick comparison with the existing ecosystem:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;DotNetPy&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;pythonnet&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;CSnakes&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;IronPython&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Native AOT&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File-based Apps&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source Generator Required&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;uv Integration&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bidirectional Calls&lt;/td&gt;
&lt;td&gt;C#→Py&lt;/td&gt;
&lt;td&gt;C#↔Py&lt;/td&gt;
&lt;td&gt;C#→Py&lt;/td&gt;
&lt;td&gt;C#↔Py&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning Curve&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DotNetPy deliberately focuses on the &lt;strong&gt;C# → Python&lt;/strong&gt; direction. If you need Python calling back into .NET objects, pythonnet remains the right choice. DotNetPy is for scenarios where .NET is the host and Python is the tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in Security
&lt;/h2&gt;

&lt;p&gt;Executing dynamic code always carries risk. DotNetPy ships with a &lt;strong&gt;Roslyn analyzer&lt;/strong&gt; that detects potential code injection at compile time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ The analyzer flags this — user input as code&lt;/span&gt;
&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Safe — user data passed as variables&lt;/span&gt;
&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"result = sum(numbers)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"numbers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userNumbers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a deliberate design choice: make the safe path easy and the dangerous path visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features at a Glance
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Python Discovery&lt;/strong&gt; — cross-platform detection of installed Python distributions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Marshaling&lt;/strong&gt; — pass .NET arrays, dictionaries, and primitives to Python and back&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variable Management&lt;/strong&gt; — capture, check, delete, and clear Python variables from C#&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free-threaded Python Support&lt;/strong&gt; — detects Python 3.13+ builds with &lt;code&gt;--disable-gil&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thread Safety&lt;/strong&gt; — automatic GIL management for safe concurrent access&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Install from NuGet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package DotNetPy &lt;span class="nt"&gt;--version&lt;/span&gt; 0.5.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;DotNetPy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/path/to/python313.dll"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sum([1,2,3,4,5])"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;GetInt32&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 15&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or let DotNetPy find Python automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PythonDiscovery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindPython&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use Cases I'm Targeting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI/ML integration&lt;/strong&gt;: Call scikit-learn, PyTorch, or Hugging Face models from a .NET service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data analysis scripts&lt;/strong&gt;: Run pandas/numpy workloads inline without a separate Python service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation &amp;amp; scripting&lt;/strong&gt;: Use &lt;code&gt;.NET 10&lt;/code&gt; file-based apps as Python-capable scripts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legacy modernization&lt;/strong&gt;: Gradually introduce Python capabilities into existing .NET applications&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The roadmap includes embeddable Python support on Windows (for simplified deployment) and specialized optimizations for AI and data science workflows. This is v0.5.0 — the API is stabilizing, and feedback at this stage is especially valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/rkttu/dotnetpy" rel="noopener noreferrer"&gt;github.com/rkttu/dotnetpy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NuGet&lt;/strong&gt;: &lt;a href="https://www.nuget.org/packages/DotNetPy" rel="noopener noreferrer"&gt;DotNetPy 0.5.0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs&lt;/strong&gt;: &lt;a href="https://github.com/rkttu/dotnetpy/blob/main/docs/USAGE.md" rel="noopener noreferrer"&gt;Usage Examples&lt;/a&gt; · &lt;a href="https://github.com/rkttu/dotnetpy/blob/main/docs/SECURITY.md" rel="noopener noreferrer"&gt;Security Guide&lt;/a&gt; · &lt;a href="https://github.com/rkttu/dotnetpy/blob/main/docs/COMPARISON.md" rel="noopener noreferrer"&gt;Comparison&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: Apache 2.0&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you're a .NET developer who's been eyeing Python's ecosystem, give DotNetPy a try. Issues, stars, and feedback are all welcome.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>python</category>
      <category>csharp</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Make Your WSL Environment Programmable</title>
      <dc:creator>Jung Hyun, Nam</dc:creator>
      <pubDate>Sat, 10 Jul 2021 16:48:28 +0000</pubDate>
      <link>https://dev.to/rkttu/make-your-wsl-environment-programmable-fi5</link>
      <guid>https://dev.to/rkttu/make-your-wsl-environment-programmable-fi5</guid>
      <description>&lt;p&gt;Original Post: &lt;a href="https://cloudeveloper.net/make-your-wsl-environment-programmable-72be5907d1ff" rel="noopener noreferrer"&gt;https://cloudeveloper.net/make-your-wsl-environment-programmable-72be5907d1ff&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have been fascinated by WSL architecture since its debut. It has the elegant and beautiful architecture to achieve interoperability between Windows and the Linux world. Eventually, I dive into the WSL Win32 APIs, its registry model, and internal too.&lt;/p&gt;

&lt;p&gt;But there was only slight WSL API documentation and sample. Later, I realized that Microsoft does not intend to use the WSL API for general purposes but WSL distribution developers. Also, the CLI tool known as WSL.exe and distro launchers is moving parts (because each Windows 10 major release has different features, command-line options). So these situations make it hard to automate the WSL environment.&lt;/p&gt;

&lt;p&gt;After many trials and errors, I developed a small but effective automation tool called WSL SDK. This tool is an out-of-process style COM server so that you can access the SDK with any COM-supported language.&lt;br&gt;
In this article, I want to share the walkthrough of the WSL SDK to overcome the difficulties of using official Win32 WSL APIs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hidden treasures of WSL
&lt;/h2&gt;

&lt;p&gt;If you are interested in the WSL APIs, you might get a hint about the API. For example, the API WslLaunch returns an HRESULT code. A WSL-related COM interface is in its internal composition known as LxssUserSession, and this API wraps around the COM object.&lt;/p&gt;

&lt;p&gt;Sadly, the internal COM object was completely hiding by Microsoft, and it looks like it was pretty intended. I guess that there are reasonable decisions about this direction. However, its internal COM object also does not have documentation well.&lt;/p&gt;
&lt;h2&gt;
  
  
  Unkindness of the WSL APIs
&lt;/h2&gt;

&lt;p&gt;Because of that, there is well-known and by-design behavior about the WSL APIs. If you call the WSL APIs with P/Invoke via PowerShell, LINQPad, or any COM-enabled environment, you cannot reach any WSL APIs. Many enthusiasts tried the APIs, but there is no luck.&lt;br&gt;
And why is that? Those environments I mentioned already initiated with another CoInitializeSecurity call. Sadly, WSL APIs require a particular initialization parameter. And the CoInitializeSecurity called in somewhere; you can never invoke the CoInitializeSecurity again. To overcome this issue, inevitably, I should choose the out-of-process model.&lt;br&gt;
Excavating the old samples&lt;br&gt;
However, the out-of-process model makes cumbersome steps to use the API. You should check the existence of the process. You will have to define how to communicate with the external process such as pipe, internal networking, or any marshaling protocols. Moreover, this approach makes it hard to extend and maintain the API calls and functionalities.&lt;/p&gt;

&lt;p&gt;I stuck at this point so a long time. But, thanks to the old project named All-In-One Code Framework developed by Microsoft, I found a beautiful solution. Yes. The out-of-process COM server model! So I adapted the sample out-of-process COM server code, and it works like a charm.&lt;/p&gt;
&lt;h2&gt;
  
  
  Under the hood
&lt;/h2&gt;

&lt;p&gt;When the client application requests a WSL SDK service object via COM API, Windows automatically launches the executable file to obtain an appropriate object reference. Then, wrap it with a proxy interface, and the client application retrieves that reference. For better understanding, please look at the below picture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1f6i983t1khey4btuza.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1f6i983t1khey4btuza.jpg" alt="Excerpted from http://docwiki.embarcadero.com/RADStudio/Sydney/en/In-process,_Out-of-process,_and_Remote_Servers" width="412" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Justly, WSL SDK executable file invokes CoInitializeAPI with a correct parameter to communicate with WSL APIs. Then start a message pump to handle external RPC requests and any Windows GUI-related requests. When an object reference is requested, its reference count will increase or decrease. Then the count reaches zero; the executable process will shut down. And again, another request arrived, the same round will occur again until unregistering the COM information from the registry.&lt;/p&gt;

&lt;p&gt;So the WSL SDK decouples the COM security model between the application's one and WSL's requirement. And process lifecycle management handled by the operating system's infrastructure and a reference count mechanism. Every WSL SDK client does not need to care about any details. They request a WSL SDK interface as usual, and all things are going well.&lt;/p&gt;
&lt;h2&gt;
  
  
  Comparing direct P/Invoke to the WSL API and using WSL SDK
&lt;/h2&gt;

&lt;p&gt;I will show a simple demonstration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6c4i8uesu1m50enuiz8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6c4i8uesu1m50enuiz8.png" alt="Comparing direct P/Invoke to the WSL API and using WSL SDK" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A PowerShell sample code looks like below. I excerpted a sample code from the GitHub issue WSL API does not work in PowerShell · Issue #4058 · microsoft/WSL (github.com).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Excerpted from https://github.com/microsoft/WSL/issues/4058&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Calling WslIsDistributionRegistered directrly (Ubuntu-20.04):'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-TypeDefinition&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sh"&gt;@'
using System.Runtime.InteropServices;
public class wslutil
{
 [DllImport("wslapi.dll", CharSet = CharSet.Unicode)]
 public static extern uint WslIsDistributionRegistered([In, MarshalAs(UnmanagedType.LPWStr)] string distributionName);
public static void Main(string[] args)
 {
  System.Console.WriteLine(WslIsDistributionRegistered("Ubuntu-20.04"));
 }
}
'@&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;wslutil&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PowerShell code references a C-function from the WSLAPI.dll. Seemingly, it makes sense and should work well. But the code returns zero, does not reflect the current status.&lt;/p&gt;

&lt;p&gt;However, WSL SDK returns the correct value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Calling WSL SDK API (Ubuntu-20.04):'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$DistroName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Ubuntu-20.04'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ComObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'WslSdk.WslService'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDistroRegistered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$DistroName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$Result&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essentially, both code depends on the WslIsDistributionRegistered function, but the PowerShell already calls the CoInitializeSecurity which does not meet requirements for WSL APIs. The first example will not work because of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demo: Sandboxing WSL distro
&lt;/h2&gt;

&lt;p&gt;I will show another, more complex example script. The below code will automatically download the Alpine Linux root filesystem image from the official mirror. Then, add the VI improved editor to the distribution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="bp"&gt;$Error&lt;/span&gt;&lt;span class="n"&gt;ActionPreference&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stop"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ComObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'WslSdk.WslService'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'A WslSdk.WslService object is created.'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Pause&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Get installed distro list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Currently installed WSL distro list: '&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDistroList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Pause&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Generate Random Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$RandomName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateRandomName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"We will use &lt;/span&gt;&lt;span class="nv"&gt;$RandomName&lt;/span&gt;&lt;span class="s2"&gt; as a new distro"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Download Alpine Linux RootFS Image&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Downloading alpine linux root file system image'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$TargetUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'https://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86_64/alpine-minirootfs-3.14.0-x86_64.tar.gz'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$RootfsFilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;TEMP&lt;/span&gt;&lt;span class="s2"&gt;\alpine.tar.gz"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$InstallPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Distro\&lt;/span&gt;&lt;span class="nv"&gt;$RandomName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-UseBasicParsing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TargetUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-OutFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$RootfsFilePath&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Pause&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Register Distro&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Distro installation begins"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" - Distro Name: &lt;/span&gt;&lt;span class="nv"&gt;$RandomName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" - Source RootFS File Path: &lt;/span&gt;&lt;span class="nv"&gt;$RootfsFilePath&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" - Destination Install Path: &lt;/span&gt;&lt;span class="nv"&gt;$InstallPath&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterDistro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$RandomName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$RootfsFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$InstallPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Pause&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Distro Register Check&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDistroRegistered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$RandomName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Distro Name &lt;/span&gt;&lt;span class="nv"&gt;$RandomName&lt;/span&gt;&lt;span class="s2"&gt; Installed: &lt;/span&gt;&lt;span class="nv"&gt;$Result&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Pause&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Metadata Query&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Querying &lt;/span&gt;&lt;span class="nv"&gt;$RandomName&lt;/span&gt;&lt;span class="s2"&gt; metadata..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueryDistroInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$RandomName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" - Distro ID: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DistroId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" - Distro Name: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DistroName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" - Environment Variabls: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DefaultEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" - Default Uid: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DefaultUid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" - Flags: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DistroFlags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" - Win32 Interop Enabled: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableInterop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" - Drive Mounting Enabled: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableDriveMounting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" - NT Path Append Enabled: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendNtPath&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" - WSL Version: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WslVersion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Pause&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Run WSL command&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Installing vim..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunWslCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DistroName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"apk add vim"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$res&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Pause&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Revealing launcher executable&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Revealing launcher executable file"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Start-Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-FilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;windir&lt;/span&gt;&lt;span class="s2"&gt;\explorer.exe"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ArgumentList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/select,&lt;/span&gt;&lt;span class="nv"&gt;$InstallPath&lt;/span&gt;&lt;span class="s2"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;$RandomName&lt;/span&gt;&lt;span class="s2"&gt;.exe"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Pause&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Unregister Distro&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Unregister &lt;/span&gt;&lt;span class="nv"&gt;$RandomName&lt;/span&gt;&lt;span class="s2"&gt; distro..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UnregisterDistro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$RandomName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Pause&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Get installed distro list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Currently installed WSL distro list: '&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDistroList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Pause&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a basic root filesystem image does not design for the WSL. As you already know, WSL supports importing any Linux root filesystem image which meets exact processor architecture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4yhloqftlop5zu86rwu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4yhloqftlop5zu86rwu.png" alt="Sandboxing WSL distro" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;WSL SDK handles dynamic distribution registration and manipulation even in the PowerShell environment.&lt;/p&gt;

&lt;p&gt;Just for fun, even in Microsoft Excel, you can interact with the WSL environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes587f80ouwao4fwjivn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes587f80ouwao4fwjivn.png" alt="Running WSL SDK via Excel with VBA" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiir2pdici2gyqi4y7tmk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiir2pdici2gyqi4y7tmk.png" alt="Automating WSL in the Microsoft VB for Applications" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can access a variety of sample codes of WSL SDKs here: &lt;a href="https://github.com/wslhub/wsl-sdk-com/tree/main/sample" rel="noopener noreferrer"&gt;https://github.com/wslhub/wsl-sdk-com/tree/main/sample&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Roadmap
&lt;/h2&gt;

&lt;p&gt;I recently created a GitHub action pipeline thanks to the &lt;a href="https://github.com/marketplace/actions/setup-wsl" rel="noopener noreferrer"&gt;https://github.com/marketplace/actions/setup-wsl&lt;/a&gt; plugin and the GitHub team's decision to enable the WSL component in their Windows Server workload.&lt;/p&gt;

&lt;p&gt;Because of that, I could create a continuous integration for WSL SDK, which makes more confident releases. (&lt;a href="https://github.com/wslhub/wsl-sdk-com/actions/workflows/wsl-sdk-com-build.yml" rel="noopener noreferrer"&gt;https://github.com/wslhub/wsl-sdk-com/actions/workflows/wsl-sdk-com-build.yml&lt;/a&gt;) This work is a significant achievement of the WSL SDK roadmap.&lt;/p&gt;

&lt;p&gt;Moreover, my bucket list contains those goals.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Registration-free COM server (If possible)&lt;/li&gt;
&lt;li&gt;ARM64 native support&lt;/li&gt;
&lt;li&gt;Adopting WSL SDK to another of my previous project (WSL Manager)&lt;/li&gt;
&lt;li&gt;Various language wrappers (C#, Python, Go-lang, PowerShell, or any COM supported languages)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are interested in the WSL SDK project, please come and contribute to the GitHub repo. (&lt;a href="https://github.com/wslhub/wsl-sdk-com" rel="noopener noreferrer"&gt;https://github.com/wslhub/wsl-sdk-com&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>wsl2</category>
      <category>linux</category>
      <category>windows</category>
      <category>devops</category>
    </item>
    <item>
      <title>Set up your SSH server in Windows 10 native way</title>
      <dc:creator>Jung Hyun, Nam</dc:creator>
      <pubDate>Sun, 02 Feb 2020 07:50:53 +0000</pubDate>
      <link>https://dev.to/rkttu/set-up-an-ssh-server-in-windows-10-native-way-22d9</link>
      <guid>https://dev.to/rkttu/set-up-an-ssh-server-in-windows-10-native-way-22d9</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/rkttu/set-up-ssh-key-and-git-integration-in-windows-10-native-way-o4i"&gt;Continuing from the last post&lt;/a&gt;, we'll look at how to set up a built-in SSH server starting with Windows 10 and Windows Server 1709. This method allows Windows Server to connect remotely using SSH, just like a traditional Linux server. We will also look at how you can use Remote Desktop securely without modifying your firewall settings using SSH port tunneling.&lt;/p&gt;

&lt;h1&gt;
  
  
  Installing and configuring OpenSSH Server
&lt;/h1&gt;

&lt;p&gt;You can install OpenSSH Server the same way you installed the SSH client in the previous article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$OpenSSHServer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Get-WindowsCapability&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Online&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;OpenSSH.Server&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Add-WindowsCapability&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Online&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$OpenSSHServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After installing the OpenSSH server program, start and stop the NT service once to create the necessary initial configuration files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$SSHDaemonSvc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Get-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;sshd&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Start-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SSHDaemonSvc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Stop-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SSHDaemonSvc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Apply asymmetric key authentication
&lt;/h1&gt;

&lt;p&gt;It is highly recommended that you use the public key authentication method and disable the password authentication method to prevent attacks through password assignment. To enable this authentication feature, start PowerShell as an administrator and open the file in the path below with notepad. (Or you can use another text editor of your choice.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nf"&gt;notepad.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;PROGRAMDATA&lt;/span&gt;&lt;span class="nx"&gt;\ssh\sshd_config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For the following items, uncomment the items and apply the value as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PubkeyAuthentication yes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PasswordAuthentication no&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PermitEmptyPasswords no&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then choose your preferred method of managing SSH public keys. Starting with Windows Server 2019 (or 1809), there are two ways to describe SSH public keys. One of which is the traditional way of creating an authorized_keys file in the user's home directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;$HOME\.ssh\authorized_keys&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To use this method, comment out the following block of code at the bottom of the configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Match Group administrators
  AuthorizedKeysFile
  __PROGRAMDATA__/ssh/administrators_authorized_keys
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then go to the user home directory you want to log in to and create a &lt;code&gt;.ssh&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;\.ssh"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Create an &lt;code&gt;authorized_keys&lt;/code&gt; file (without the extension) inside the newly created directory and open it with your favorite text editor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$authorizedKeyFilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nf"&gt;\.ssh\authorized_keys&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$authorizedKeyFilePath&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;notepad.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$authorizedKeyFilePath&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Add the SSH public key value you are using here.&lt;/p&gt;

&lt;p&gt;When you save the file, you must change the file permission settings as described in the section &lt;strong&gt;Setting File Permissions with Authentication Key Information&lt;/strong&gt;. If this setting is missing, the SSH connection will fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;administrators_authorized_keys&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is the default used by OpenSSH included in Windows Server 2019 (1809). Instead of registering a new SSH key for each user, you can manage your files in one place.&lt;/p&gt;

&lt;p&gt;If you use this method, all public keys must be stored in the &lt;code&gt;$env: PROGRAMDATA\ssh\administrators_authorized_keys&lt;/code&gt; file, except for non-administrative users (that is, users who do not belong to the Administrators group). If you try, this setting will be used instead of the setting in your home directory, so if there is no key here, the connection will fail.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;administrators_authorized_keys&lt;/code&gt; file does not exist by default and must be created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$authorizedKeyFilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;ProgramData&lt;/span&gt;&lt;span class="nf"&gt;\ssh\administrators_authorized_keys&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$authorizedKeyFilePath&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;notepad.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$authorizedKeyFilePath&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Add the SSH public key value you are using here.&lt;/p&gt;

&lt;p&gt;When you save the file, you must change the file permission settings as described in the section &lt;strong&gt;Setting File Permissions with Authentication Key Information&lt;/strong&gt;. If this setting is missing, the SSH connection will fail.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting File Permissions with Authentication Key Information
&lt;/h1&gt;

&lt;p&gt;A common and very tough problem that you will face about using the OpenSSH server for Windows is this. SSH key file permission should have correct and limited file permission. Windows version of SSH also follows this rule, but especially in Windows, configuring file permission can be unintuitive.&lt;/p&gt;

&lt;p&gt;Depending on the method you chose in the previous step, you must verify the path of the &lt;code&gt;authorized_keys&lt;/code&gt; file or &lt;code&gt;administrators_authorized_keys&lt;/code&gt; file and change the permissions so that only the system account can access it using the &lt;code&gt;icacls.exe&lt;/code&gt; utility and the &lt;code&gt;Get-Acl&lt;/code&gt; and &lt;code&gt;Set-Acl&lt;/code&gt; commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$authorizedKeyFilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;icacls.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$authorizedKeyFilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;NT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AUTHORITY\Authenticated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Users&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;icacls.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$authorizedKeyFilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/inheritance:r&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Get-Acl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;ProgramData&lt;/span&gt;&lt;span class="nx"&gt;\ssh\ssh_host_dsa_key&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Set-Acl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$authorizedKeyFilePath&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Changing the SSH Default Shell
&lt;/h1&gt;

&lt;p&gt;Basically, for compatibility reasons, the Windows operating system has provided a shell-based interpreter that recognizes DOS commands for a long time. But now, with more and more features than working with DOS commands, PowerShell is becoming a good alternative.&lt;/p&gt;

&lt;p&gt;If necessary, you can specify that PowerShell as the default shell for SSH instead of the DOS interpreter. However, the settings here are specific to SSH sessions, not for the Remote Desktop or console session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nf"&gt;New-ItemProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HKLM:\SOFTWARE\OpenSSH"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DefaultShell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;WINDIR&lt;/span&gt;&lt;span class="s2"&gt;\System32\WindowsPowerShell\v1.0\powershell.exe"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-PropertyType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Staring SSH Server
&lt;/h1&gt;

&lt;p&gt;You are now ready to start your SSH server. The SSH server is set to manual run by default, so you can change the startup mode to automatic. Then starts the service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$SSHDaemonSvc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Get-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;sshd&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Set-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SSHDaemonSvc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-StartupType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Automatic&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Start-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SSHDaemonSvc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LDgkf4-s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/4388/1%2AgJry9InM3arljQZjgdKP9A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LDgkf4-s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/4388/1%2AgJry9InM3arljQZjgdKP9A.png" alt="SSH to Windows Server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations! From now, you can connect to Windows with SSH-key authentication.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Secure Remote Desktops
&lt;/h1&gt;

&lt;p&gt;Unlike Linux, Windows still runs much of the system on a graphical interface rather than on the command line. So if you try to do something with a terminal like this, you may not have much to do as you might expect.&lt;/p&gt;

&lt;p&gt;Remote Desktop, however, is a well-known food for many hackers and script kiddies as is well known. You may encounter the dilemma of choosing between convenience and security.&lt;/p&gt;

&lt;p&gt;Fortunately, SSH provides the concept of tunneling, supporting the ability to relay other network connections securely. Remote desktop connections can also be protected in this way so that you can use them with confidence.&lt;/p&gt;

&lt;p&gt;Start by blocking the TCP 3389 and UDP 3389 ports. You can do this because you will use Remote Desktop only with SSH tunneling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nf"&gt;Set-NetFirewallRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DisplayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;Remote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Desktop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Mode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UDP-In&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Block&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Set-NetFirewallRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DisplayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;Remote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Desktop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Mode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TCP-In&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Block&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, you must change the registry flag value so that the remote desktop server can accept the connection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nf"&gt;Set-ItemProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;HKLM:\System\CurrentControlSet\Control\Terminal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;fDenyTSConnections&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And the part that I'm going to explain right now is really cool. As you saw earlier, you will not be asked for your password when you connect to OpenSSH, so you can set up a randomly generated strong password each time you use it. It is very useful to have a simple script in the system directory that can do this.&lt;/p&gt;

&lt;p&gt;Choose the type of script you want to create a file called &lt;code&gt;ChangePassword.ps1&lt;/code&gt;. We will keep the file in the system directory for your convenience.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$change_pwd_script_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;WINDIR&lt;/span&gt;&lt;span class="nf"&gt;\ChangePassword.ps1&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Clear-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$change_pwd_script_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SilentlyContinue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;notepad.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$change_pwd_script_path&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Set your desired password
&lt;/h2&gt;

&lt;p&gt;Create the contents of the &lt;code&gt;ChangePassword.ps1&lt;/code&gt; file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$Password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Read-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Prompt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Provide your new account password"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AsSecureString&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Set-LocalUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;UserName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Password&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Clear-Variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Password"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Detailed settings such as remote desktop settings, WinRM connection settings, and Windows Update can be controlled using the sconfig command."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This script allows you to enter your own password. However, unless you change the policy, you can only use passwords that pass the Windows Server enhanced default password rules. You must specify a password that must meet all of the following conditions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;English capital letters (A through Z)&lt;/li&gt;
&lt;li&gt;Lowercase English letters (a through z)&lt;/li&gt;
&lt;li&gt;Arabic numerals (0 to 9)&lt;/li&gt;
&lt;li&gt;Special symbols (e.g.!, $, #,%)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Generate a new password every time
&lt;/h2&gt;

&lt;p&gt;Create the contents of the &lt;code&gt;ChangePassword.ps1&lt;/code&gt; file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nf"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Web&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$Password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;System.Web.Security.&lt;/span&gt;&lt;span class="kt"&gt;Membership&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="nf"&gt;GeneratePassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Set-LocalUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;UserName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ConvertTo-SecureString&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AsPlainText&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your New Password is:&lt;/span&gt;&lt;span class="se"&gt;`r`n`r`n&lt;/span&gt;&lt;span class="nv"&gt;$Password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Detailed settings such as remote desktop settings, WinRM connection settings, and Windows Update can be controlled using the sconfig command."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nf"&gt;Clear-Variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Password"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This way, you can set a strong password every time. If you forget your password, you can rest assured that you can still use public key authentication as a secondary means of authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try logging in to Remote Desktop with Tunneling
&lt;/h2&gt;

&lt;p&gt;Now enter the following command to run the above script. After that, just set the password as guided by the script and verify that the remote desktop connection is working.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nf"&gt;ChangePassword&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To log in to the remote desktop, run SSH as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 3389:localhost:3389 &amp;lt;user_id&amp;gt;@&amp;lt;host_address&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The first 3389 is the port number on the server-side, and the second address is the port number you want to use locally. If you have changed the remote desktop's port number from the registry to another number on the server, you can enter the changed port number instead of 3389.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jOKYzAKh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/5912/1%2AO4WNnaXe9_LaeQ2Ed5s_6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jOKYzAKh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/5912/1%2AO4WNnaXe9_LaeQ2Ed5s_6g.png" alt="Access to remote desktop using SSH tunneling with Remote Desktop 10 on macOS"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try to connect to a remote desktop using only the &lt;code&gt;&amp;lt;host_address&amp;gt;&lt;/code&gt; part as before, the firewall will block the connection as previously set up.  So no one can access the remote desktop directly unless the user has registered a public key that matches the SSH secret key with that server.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using secure file sending and receiving
&lt;/h1&gt;

&lt;p&gt;Not surprisingly, it is possible to use SSH based SFTP. This feature can be used to securely handle large file transfers in place of the remote desktop's folder sharing feature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uWj1M1nT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/5080/1%2AD-5vXZxRln1qBEFrzX0ihQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uWj1M1nT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/5080/1%2AD-5vXZxRln1qBEFrzX0ihQ.png" alt="Access to Windows Server using FileZilla's SFTP feature"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any client that supports the SFTP feature, such as FileZilla, is compatible and has an management advantage, as there is no need to apply complex firewall open policies like traditional FTP.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrap-up
&lt;/h1&gt;

&lt;p&gt;This walks you through all the new SSH features that have been added since Windows 10 1709. Both articles are available for Windows 10 and Windows Server 2019, so please take a moment to set them up for even more security.&lt;/p&gt;

&lt;h1&gt;
  
  
  Credits
&lt;/h1&gt;

&lt;p&gt;The following articles helped me as I wrote this article.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ilovepowershell.com/2018/05/28/awesome-and-simple-way-to-generate-random-passwords-with-powershell/"&gt;https://ilovepowershell.com/2018/05/28/awesome-and-simple-way-to-generate-random-passwords-with-powershell/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/16212816/setting-up-openssh-for-windows-using-public-key-authentication"&gt;https://stackoverflow.com/questions/16212816/setting-up-openssh-for-windows-using-public-key-authentication&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>openssh</category>
      <category>sftp</category>
      <category>rdp</category>
      <category>windowsserver</category>
    </item>
    <item>
      <title>Set up SSH Key and Git integration in Windows 10 native way</title>
      <dc:creator>Jung Hyun, Nam</dc:creator>
      <pubDate>Sun, 27 Oct 2019 16:18:36 +0000</pubDate>
      <link>https://dev.to/rkttu/set-up-ssh-key-and-git-integration-in-windows-10-native-way-o4i</link>
      <guid>https://dev.to/rkttu/set-up-ssh-key-and-git-integration-in-windows-10-native-way-o4i</guid>
      <description>&lt;p&gt;Did you know that Windows 10 comes with an OpenSSH client?&lt;/p&gt;

&lt;p&gt;Starting with the Windows 10 Fall Creators Update (1709), OpenSSH clients are starting to be offered as Windows add-ons. However, it is easy to misunderstand that it is only provided by unfamiliar usage that differs from Linux, or that it is still not supported properly.&lt;/p&gt;

&lt;p&gt;In this article, we'll look at how to set up the commonly used client environment related to OpenSSH in a way that's built into Windows 10, and I'll give you some useful tips.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configure Windows OpenSSH
&lt;/h1&gt;

&lt;p&gt;Start PowerShell as an administrator and use the PowerShell commands below to add Windows components.&lt;/p&gt;

&lt;p&gt;Microsoft's current installation of OpenSSH is an add-on package, Feature-On-Demand, not an item in the &lt;code&gt;Add/Remove Windows Components&lt;/code&gt; dialog box of the classic Control Panel &lt;code&gt;control.exe&lt;/code&gt;. You can install it only by using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$OpenSSHClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-WindowsCapability&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Online&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;OpenSSH.Client&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Add-WindowsCapability&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Online&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$OpenSSHClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally, no system restart is required after installation.&lt;/p&gt;

&lt;p&gt;After completing the installation, you may enable the ssh-agent service. This service is used to register to not ask for the SSH key password every time. Initially, the service is disabled and stopped, so set the service to autostart, and start it now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$SSHAgentSvc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;ssh-agent&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SSHAgentSvc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-StartupType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Automatic&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Start-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SSHAgentSvc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now close the PowerShell window in administrator mode and work with the PowerShell window open as normal.&lt;/p&gt;

&lt;p&gt;Since we are setting up a new system, let's create a new SSH key. Some common utilities have been added along with the OpenSSH client package. Run the &lt;code&gt;ssh-keygen&lt;/code&gt; command and answer questions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;ssh-keygen&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a key pair from the &lt;code&gt;$HOME\.ssh\id_rsa&lt;/code&gt; file and the &lt;code&gt;$HOME\.ssh\id_rsa.pub&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Now run the &lt;code&gt;ssh-add&lt;/code&gt; command to add this key pair to the ssh-agent service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;ssh-add&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It automatically registers the &lt;code&gt;$HOME\.ssh\id_rsa&lt;/code&gt; key pair, and now you can authenticate with that key pair.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Sometimes the system may have several &lt;code&gt;ssh.exe&lt;/code&gt; binary files installed, and the &lt;code&gt;ssh.exe&lt;/code&gt; binary path may be duplicated in the &lt;code&gt;PATH&lt;/code&gt; environment variable. To debug this problem, review the contents of the &lt;code&gt;path&lt;/code&gt; command or the &lt;code&gt;PATH&lt;/code&gt; environment variable and change the folder path containing the &lt;code&gt;ssh.exe&lt;/code&gt; binary to be used first, or keep only one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Registering SSH Keys on Github
&lt;/h1&gt;

&lt;p&gt;You need to register the public key of this SSH Key Pair to Github or your Git repository.&lt;/p&gt;

&lt;p&gt;Enter the following PowerShell command to copy the public key value to register other systems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;\.ssh\id_rsa.pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-Clipboard&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this command, public key automatically entered on the clipboard.&lt;/p&gt;

&lt;p&gt;Then enter the following command to open the GitHub configuration page. (Or you can open the URL below directly in your preferred browser instead of your default browser.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Start-Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;https://github.com/settings/ssh/new&lt;/span&gt;&lt;span class="s1"&gt;'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, paste the public key from the clipboard and register it by adding a clear description of the key.&lt;/p&gt;

&lt;h1&gt;
  
  
  Install Git Client and SSH Client
&lt;/h1&gt;

&lt;p&gt;There are many ways to install the Git client, but I personally recommend the Chocolatey Package Manager as the most intuitive and easy way.&lt;/p&gt;

&lt;p&gt;The official Git client installation package exposes a lot of options that can cause side effects, so if you install it incorrectly, you may run into difficulties due to unintended features.&lt;/p&gt;

&lt;p&gt;First install the Chocolatey Package Manager if it is not already installed. Because this is a system-level addition of software, allow a few minutes to open a new PowerShell window as an administrator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Set-ExecutionPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Bypass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Net.WebClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DownloadString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;https://chocolatey.org/install.ps1&lt;/span&gt;&lt;span class="s1"&gt;'))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now enter the command to install the Git for Windows client.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Often, if the Chocolatey.org website enters a regular checkout period, the installation may not proceed properly. In this case, check the Chocolatey.org website and try again later.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;choco&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-y&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back in the regular PowerShell window, set the GIT_SSH environment variable. You must specify this environment variable so that Git clients can properly recognize SSH clients on Windows 10.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$SSHPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;ssh.exe&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Source&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="nx"&gt;SetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="n"&gt;GIT_SSH&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SSHPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Powering PowerShell with oh-my-posh
&lt;/h1&gt;

&lt;p&gt;As the name suggests, oh-my-posh is a Windows PowerShell version of oh-my-zsh that is popular on macOS and Linux these days. And interestingly, it supports some of the features that oh-my-zsh provides.&lt;/p&gt;

&lt;p&gt;First run the oh-my-posh installation script. It's listed in PowerShell's official module repository, so the commands are not complicated and simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Install-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;posh-git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CurrentUser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Install-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;oh-my-posh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CurrentUser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are installing on PowerShell Core for Windows (6.x or later), run the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Install-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PSReadLine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AllowPrerelease&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CurrentUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-SkipPublisherCheck&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit the Profile file so that the oh-my-posh shell can be loaded automatically when PowerShell starts. If you run the command below, the file will be created if it doesn't exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$PROFILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$PROFILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;notepad.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$PROFILE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following code at the end of the script and save it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Import-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;posh-git&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Import-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;oh-my-posh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-Theme&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Paradox&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a side note, using a programming font with powerline patching looks pretty and doesn't break the glyphs. Run the following command again in an elevated PowerShell and update the console window's font settings with the D2Coding font.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Import-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;posh-git&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Import-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;oh-my-posh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-Theme&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Paradox&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When all the settings are applied, you will see something like the picture below, and if you move the directory to the Git repository, you will see the branch name look good. This seems to have some assortment. 😎&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4zs154z63bur997lse0d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4zs154z63bur997lse0d.png" alt="oh-my-posh in action!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Appendix 1: Installing the Windows Terminal App
&lt;/h1&gt;

&lt;p&gt;You can go directly to the Windows Terminal app store page by running the following command in PowerShell: As is well known, using Windows Terminal gives you all the benefits of a modern CLI development environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Start-Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'https://www.microsoft.com/store/productId/9N0DX20HK701'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, you can launch the Windows Terminal app directly with &lt;code&gt;wt.exe&lt;/code&gt; or &lt;code&gt;wt&lt;/code&gt; shortcuts. That is, on Windows, remember &lt;code&gt;wt&lt;/code&gt; instead of &lt;code&gt;cmd&lt;/code&gt;, &lt;code&gt;powershell&lt;/code&gt; or &lt;code&gt;pwsh&lt;/code&gt;. 😏&lt;/p&gt;

&lt;h1&gt;
  
  
  Appendix 2: For SourceTree
&lt;/h1&gt;

&lt;p&gt;Unfortunately, the Git client used by SourceTree does not work with the SSH Agent service provided by Windows. Instead, you can use the keys you created.&lt;br&gt;
In the SourceTree Options window, change the SSH client to OpenSSH as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqabitx4yj5jgvekiomq0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqabitx4yj5jgvekiomq0.png" alt="Setting up existing SSH key on SourceTree"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, verify that the SSH key is the same as the &lt;code&gt;$HOME\.ssh\id_rsa&lt;/code&gt; file created in the previous step. If it is different, specify it again.&lt;br&gt;
When done, press the OK button to save the settings.&lt;/p&gt;

&lt;h1&gt;
  
  
  Appendix 3: Integrating with Visual Studio Code
&lt;/h1&gt;

&lt;p&gt;If &lt;code&gt;GIT_SSH&lt;/code&gt; environment variable is registered properly, integration is completed without any special setting. However, even though you have completed the configuration, if you are still in progress without any message when performing git pull, you can run the &lt;code&gt;ssh-add -l&lt;/code&gt; command built-in terminal to check the connection status with the ssh-agent service.&lt;/p&gt;

</description>
      <category>windows10</category>
      <category>ssh</category>
      <category>git</category>
      <category>github</category>
    </item>
  </channel>
</rss>
