Added samples to the API (#40930)
* Added samples for the WSLc API
Craig Loewen committed
Jul 7, 2026 at 19:33 UTC
4f569ffc8a355dfe9a542456aec0e379f8944d64
21 files changed
+1134
doc/samples/.gitignore
new
+37
@@ -0,0 +1,37 @@
1
+# Build outputs and restored packages for the WSL Container API samples.
2
+packages/
3
+bin/
4
+obj/
5
+x64/
6
+x86/
7
+ARM64/
8
+Win32/
9
+Debug/
10
+Release/
11
+*.exe
12
+*.pdb
13
+*.ilk
14
+*.obj
15
+*.log
16
+*.tlog
17
+*.user
18
+
19
+# Runtime data created when the samples are run.
20
+WslcNextcloudStorage/
21
+WslcNextcloudData/
22
+WslcStorage/
23
+WslcQrStorage/
24
+*.tar
25
+*_out.txt
26
+*_err.txt
27
+out*.txt
28
+err*.txt
29
+run_nextcloud_test.ps1
30
+
31
+# The repository-root .gitignore excludes project files (*.sln, *.csproj,
32
+# *.vcxproj, *.filters) because they are normally generated by CMake. These
33
+# samples are hand-authored standalone projects, so re-include their sources.
34
+!*.sln
35
+!*.csproj
36
+!*.vcxproj
37
+!*.filters
doc/samples/README.md
new
+11
@@ -0,0 +1,11 @@
1
+# WSL Container API samples
2
+
3
+Standalone, self-contained samples that demonstrate the
4
+[WSL Container API](https://aka.ms/wslc) (`Microsoft.WSL.Containers`).
5
+
6
+| Sample | Language | Description |
7
+| --- | --- | --- |
8
+| [WSLC-HelloWorld](WSLC-HelloWorld) | C | Minimal sample that runs `echo` in an `alpine` container and prints its output. |
9
+| [WSLC-Neofetch](WSLC-Neofetch) | C++/WinRT | Runs the Linux `neofetch` command from a native Windows `.exe`. |
10
+| [WSLC-NextCloud](WSLC-NextCloud) | C# | Runs a Nextcloud server in a container, exposed on `http://localhost:8080`. |
11
+| [WSLC-CustomContainer](WSLC-CustomContainer) | C# CLI | Generates a scannable QR code in your terminal with a Python tool in a **custom Containerfile that is auto-built at F5**. |
doc/samples/WSLC-CustomContainer/Container/Containerfile
new
+18
@@ -0,0 +1,18 @@
1
+# Custom container image for the WSLC-CustomContainer sample.
2
+#
3
+# A tiny Python tool that turns text (e.g. a URL) into a scannable QR code drawn
4
+# with Unicode block characters, printed straight to the terminal.
5
+#
6
+# This image is built automatically when the C# project is built, via the
7
+# <WslcImage> item in WSLCCustomContainer.csproj (no manual docker/wslc steps).
8
+
9
+FROM python:3.11-slim
10
+
11
+# qrcode renders ASCII/Unicode QR codes with no extra native dependencies.
12
+RUN pip install --no-cache-dir qrcode
13
+
14
+COPY qr.py /app/qr.py
15
+
16
+# No ENTRYPOINT/CMD: the host keeps the container alive with its own init
17
+# process (sleep) and then execs `python /app/qr.py <text>` to render the code.
18
+# (Run it directly with: wslc run customcontainer python /app/qr.py "your text".)
doc/samples/WSLC-CustomContainer/Container/qr.py
new
+32
@@ -0,0 +1,32 @@
1
+#!/usr/bin/env python3
2
+"""Turn text (e.g. a URL) into a scannable QR code printed to the terminal.
3
+
4
+Used by the WSLC-CustomContainer sample. The Windows host passes the text to
5
+encode as command-line arguments; the QR is drawn with Unicode block characters
6
+so it scans straight from the terminal.
7
+"""
8
+
9
+import sys
10
+
11
+import qrcode
12
+
13
+
14
+def main() -> int:
15
+ text = " ".join(sys.argv[1:]).strip()
16
+ if not text:
17
+ print("usage: qr.py <text-or-url>", file=sys.stderr)
18
+ return 2
19
+
20
+ qr = qrcode.QRCode(border=2)
21
+ qr.add_data(text)
22
+ qr.make(fit=True)
23
+
24
+ print(f"QR code for: {text}\n")
25
+ # invert=True renders dark modules as spaces on a light background, which
26
+ # scans reliably in terminals with a dark color scheme.
27
+ qr.print_ascii(invert=True)
28
+ return 0
29
+
30
+
31
+if __name__ == "__main__":
32
+ sys.exit(main())
doc/samples/WSLC-CustomContainer/Program.cs
new
+117
@@ -0,0 +1,117 @@
1
+// WSLC-CustomContainer
2
+//
3
+// A barebones Windows console application that turns text (e.g. a URL) into a
4
+// scannable QR code, rendered by a tiny Python tool running inside a *custom*
5
+// Linux container.
6
+//
7
+// What makes this sample different from the others: it ships its own
8
+// Containerfile that is built automatically as part of the normal build (F5)
9
+// via the <WslcImage> item in WSLCCustomContainer.csproj. The built image is
10
+// saved to customcontainer.tar next to the executable, and this app loads that
11
+// local tar (no registry pull) before running the tool.
12
+//
13
+// dotnet run -- "https://aka.ms/wslc"
14
+
15
+using Microsoft.WSL.Containers;
16
+
17
+const string imageName = "customcontainer:latest";
18
+
19
+// The text to encode comes from the command line; fall back to a sample URL.
20
+string text = args.Length > 0 ? string.Join(' ', args) : "https://aka.ms/wslc";
21
+
22
+// Everything lives beside the executable — no hard-coded absolute paths. The
23
+// container image tar is produced next to the exe by the build.
24
+string baseDir = AppContext.BaseDirectory;
25
+string sessionPath = Path.Combine(baseDir, "WslcQrStorage");
26
+string imageTarPath = Path.Combine(baseDir, "customcontainer.tar");
27
+
28
+if (!File.Exists(imageTarPath))
29
+{
30
+ Console.Error.WriteLine($"[wslc] Image tar not found: {imageTarPath}");
31
+ Console.Error.WriteLine("[wslc] Build the project first so the custom image is auto-built.");
32
+ return 1;
33
+}
34
+
35
+int exitCode = 1;
36
+using var stopEvent = new ManualResetEventSlim(false);
37
+var consoleLock = new object();
38
+using Stream stdout = Console.OpenStandardOutput();
39
+using Stream stderr = Console.OpenStandardError();
40
+
41
+void Write(Stream target, byte[] data)
42
+{
43
+ lock (consoleLock)
44
+ {
45
+ target.Write(data, 0, data.Length);
46
+ target.Flush();
47
+ }
48
+}
49
+
50
+try
51
+{
52
+ // ---- Session ----
53
+ Console.Error.WriteLine("[wslc] Creating session...");
54
+ var sessionSettings = new SessionSettings("WSLCCustomContainer", sessionPath)
55
+ {
56
+ CpuCount = 2,
57
+ MemorySizeInMB = 2048,
58
+ VhdRequirements = new VhdOptions(string.Empty, 4UL * 1024 * 1024 * 1024, VhdType.Dynamic),
59
+ };
60
+
61
+ using var session = new Session(sessionSettings);
62
+ session.Start();
63
+
64
+ // ---- Load the locally built image from the tar (no registry pull) ----
65
+ Console.Error.WriteLine($"[wslc] Loading image from {Path.GetFileName(imageTarPath)}...");
66
+ session.LoadImage(imageTarPath);
67
+
68
+ // ---- Create & start container ----
69
+ Console.Error.WriteLine("[wslc] Starting container...");
70
+
71
+ // The init process keeps the container alive while we exec our tool.
72
+ var initProcess = new ProcessSettings
73
+ {
74
+ CommandLine = new List<string> { "/bin/sleep", "infinity" },
75
+ };
76
+
77
+ var containerSettings = new ContainerSettings(imageName)
78
+ {
79
+ InitProcess = initProcess,
80
+ EnableAutoRemove = true,
81
+ };
82
+
83
+ using var container = session.CreateContainer(containerSettings);
84
+ container.Start();
85
+
86
+ // ---- Exec the QR tool, passing the text to encode ----
87
+ Console.Error.WriteLine($"[wslc] Generating QR code for: {text}");
88
+ Console.Error.WriteLine();
89
+
90
+ var processSettings = new ProcessSettings
91
+ {
92
+ CommandLine = new List<string> { "python", "/app/qr.py", text },
93
+ OutputMode = ProcessOutputMode.Event,
94
+ };
95
+
96
+ using var process = container.CreateProcess(processSettings);
97
+ process.OutputReceived += data => Write(stdout, data);
98
+ process.ErrorReceived += data => Write(stderr, data);
99
+ process.Exited += code =>
100
+ {
101
+ exitCode = code;
102
+ stopEvent.Set();
103
+ };
104
+
105
+ process.Start();
106
+ stopEvent.Wait();
107
+
108
+ Console.Error.WriteLine("[wslc] Shutting down...");
109
+ container.Stop(Signal.SIGTERM, TimeSpan.FromSeconds(10));
110
+ session.Terminate();
111
+}
112
+catch (Exception ex)
113
+{
114
+ Console.Error.WriteLine($"[wslc] Error: {ex.Message}");
115
+}
116
+
117
+return exitCode;
doc/samples/WSLC-CustomContainer/README.md
new
+52
@@ -0,0 +1,52 @@
1
+# WSLC-CustomContainer
2
+
3
+A barebones C# console sample that turns text (e.g. a URL) into a **scannable QR
4
+code** printed to your terminal — rendered by a tiny Python tool running inside
5
+a **custom Linux container**, using the `Microsoft.WSL.Containers` SDK.
6
+
7
+Unlike the other samples (which pull public images), this one ships its own
8
+`Containerfile` that is **built automatically as part of the normal build (F5)**
9
+via the SDK's `<WslcImage>` MSBuild item — no manual `docker`/`wslc` steps. The
10
+app then loads that locally built image from a tar (no registry pull) and runs
11
+`qr.py` inside the container.
12
+
13
+## How the auto-build works
14
+
15
+`WSLCCustomContainer.csproj` declares:
16
+
17
+```xml
18
+<WslcImage Include="customcontainer"
19
+ Image="customcontainer:latest"
20
+ Dockerfile="Container\Containerfile"
21
+ Context="Container"
22
+ Sources="Container"
23
+ TarLocation="$(OutDir)customcontainer.tar" />
24
+```
25
+
26
+After `Build`, the package runs `wslc image build` + `wslc image save`,
27
+producing `customcontainer.tar` next to the executable. The step is incremental:
28
+it only re-runs when files under `Container\` change. (Requires the `wslc` CLI,
29
+installed by WSL — `wsl --install --no-distribution`.)
30
+
31
+## Build
32
+
33
+Requires the .NET 8 SDK. From this folder:
34
+
35
+```
36
+dotnet build -c Debug
37
+```
38
+
39
+## Run
40
+
41
+```
42
+dotnet run -- "https://aka.ms/wslc"
43
+```
44
+
45
+Pass any text or URL; with no argument it encodes a sample URL. The QR is drawn
46
+with Unicode blocks so you can scan it straight from the terminal.
47
+
48
+## Storage
49
+
50
+Everything lives next to the executable (no absolute paths): `WslcQrStorage\`
51
+holds the ephemeral session VHD, and `customcontainer.tar` is the auto-built
52
+image.
doc/samples/WSLC-CustomContainer/WSLCCustomContainer.csproj
new
+37
@@ -0,0 +1,37 @@
1
+<Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <OutputType>Exe</OutputType>
5
+ <TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
6
+ <Nullable>enable</Nullable>
7
+ <ImplicitUsings>enable</ImplicitUsings>
8
+ <RootNamespace>WSLCCustomContainer</RootNamespace>
9
+ <AssemblyName>qr</AssemblyName>
10
+ <!-- The native wslcsdk.dll shipped by the SDK package is architecture
11
+ specific, and the package picks which copy to deploy from PlatformTarget.
12
+ Default to x64 so a plain `dotnet build` / `dotnet run` works out of the
13
+ box. On an ARM64 device build with `dotnet build -p:PlatformTarget=ARM64`. -->
14
+ <PlatformTarget Condition="'$(PlatformTarget)' == ''">x64</PlatformTarget>
15
+ </PropertyGroup>
16
+
17
+ <ItemGroup>
18
+ <PackageReference Include="Microsoft.WSL.Containers" Version="2.9.3" />
19
+ </ItemGroup>
20
+
21
+ <!--
22
+ Auto-build the custom container image as part of the normal build (F5).
23
+ The Microsoft.WSL.Containers package runs `wslc image build` + `wslc image
24
+ save` after Build, producing customcontainer.tar next to the executable.
25
+ The app loads that local tar at run time (no registry pull). The image step
26
+ is incremental: it only re-runs when files under Container\ change.
27
+ -->
28
+ <ItemGroup>
29
+ <WslcImage Include="customcontainer"
30
+ Image="customcontainer:latest"
31
+ Dockerfile="Container\Containerfile"
32
+ Context="Container"
33
+ Sources="Container"
34
+ TarLocation="$(OutDir)customcontainer.tar" />
35
+ </ItemGroup>
36
+
37
+</Project>
doc/samples/WSLC-HelloWorld/README.md
new
+25
@@ -0,0 +1,25 @@
1
+# WSLC-HelloWorld
2
+
3
+The simplest [WSL Container API](https://aka.ms/wslc) sample, written in **C**
4
+using the flat C API (`wslcsdk.h`). Running `helloworld.exe` starts a lightweight
5
+WSL container from a small Linux image (`alpine:latest`), runs `echo` inside it,
6
+and prints the output to your terminal.
7
+
8
+## Build
9
+
10
+Open `WSLCHelloWorld.sln` in Visual Studio and build (x64), or from a developer
11
+command prompt:
12
+
13
+```
14
+nuget restore WSLCHelloWorld.sln
15
+msbuild WSLCHelloWorld.sln /p:Configuration=Debug /p:Platform=x64
16
+```
17
+
18
+## Run
19
+
20
+```
21
+x64\Debug\helloworld.exe
22
+```
23
+
24
+You should see `Hello, World from a WSL container!` printed to stdout. Progress
25
+messages (`[wslc] ...`) go to stderr.
doc/samples/WSLC-HelloWorld/WSLCHelloWorld.sln
new
+27
@@ -0,0 +1,27 @@
1
+Microsoft Visual Studio Solution File, Format Version 12.00
2
+# Visual Studio Version 17
3
+VisualStudioVersion = 17.14.37027.9 d17.14
4
+MinimumVisualStudioVersion = 10.0.40219.1
5
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WSLCHelloWorld", "WSLCHelloWorld.vcxproj", "{2B5C2E11-2C0A-4F1B-9F3D-7E8A1C2D3E4F}"
6
+EndProject
7
+Global
8
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
9
+ Debug|ARM64 = Debug|ARM64
10
+ Debug|x64 = Debug|x64
11
+ Release|ARM64 = Release|ARM64
12
+ Release|x64 = Release|x64
13
+ EndGlobalSection
14
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
15
+ {2B5C2E11-2C0A-4F1B-9F3D-7E8A1C2D3E4F}.Debug|ARM64.ActiveCfg = Debug|ARM64
16
+ {2B5C2E11-2C0A-4F1B-9F3D-7E8A1C2D3E4F}.Debug|ARM64.Build.0 = Debug|ARM64
17
+ {2B5C2E11-2C0A-4F1B-9F3D-7E8A1C2D3E4F}.Debug|x64.ActiveCfg = Debug|x64
18
+ {2B5C2E11-2C0A-4F1B-9F3D-7E8A1C2D3E4F}.Debug|x64.Build.0 = Debug|x64
19
+ {2B5C2E11-2C0A-4F1B-9F3D-7E8A1C2D3E4F}.Release|ARM64.ActiveCfg = Release|ARM64
20
+ {2B5C2E11-2C0A-4F1B-9F3D-7E8A1C2D3E4F}.Release|ARM64.Build.0 = Release|ARM64
21
+ {2B5C2E11-2C0A-4F1B-9F3D-7E8A1C2D3E4F}.Release|x64.ActiveCfg = Release|x64
22
+ {2B5C2E11-2C0A-4F1B-9F3D-7E8A1C2D3E4F}.Release|x64.Build.0 = Release|x64
23
+ EndGlobalSection
24
+ GlobalSection(SolutionProperties) = preSolution
25
+ HideSolutionNode = FALSE
26
+ EndGlobalSection
27
+EndGlobal
doc/samples/WSLC-HelloWorld/WSLCHelloWorld.vcxproj
new
+72
@@ -0,0 +1,72 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3
+ <ItemGroup Label="ProjectConfigurations">
4
+ <ProjectConfiguration Include="Debug|x64">
5
+ <Configuration>Debug</Configuration>
6
+ <Platform>x64</Platform>
7
+ </ProjectConfiguration>
8
+ <ProjectConfiguration Include="Release|x64">
9
+ <Configuration>Release</Configuration>
10
+ <Platform>x64</Platform>
11
+ </ProjectConfiguration>
12
+ <ProjectConfiguration Include="Debug|ARM64">
13
+ <Configuration>Debug</Configuration>
14
+ <Platform>ARM64</Platform>
15
+ </ProjectConfiguration>
16
+ <ProjectConfiguration Include="Release|ARM64">
17
+ <Configuration>Release</Configuration>
18
+ <Platform>ARM64</Platform>
19
+ </ProjectConfiguration>
20
+ </ItemGroup>
21
+ <PropertyGroup Label="Globals">
22
+ <VCProjectVersion>17.0</VCProjectVersion>
23
+ <Keyword>Win32Proj</Keyword>
24
+ <ProjectGuid>{2B5C2E11-2C0A-4F1B-9F3D-7E8A1C2D3E4F}</ProjectGuid>
25
+ <RootNamespace>WSLCHelloWorld</RootNamespace>
26
+ <TargetName>helloworld</TargetName>
27
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
28
+ </PropertyGroup>
29
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
30
+ <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
31
+ <ConfigurationType>Application</ConfigurationType>
32
+ <UseDebugLibraries>true</UseDebugLibraries>
33
+ <PlatformToolset>v145</PlatformToolset>
34
+ <CharacterSet>Unicode</CharacterSet>
35
+ </PropertyGroup>
36
+ <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
37
+ <ConfigurationType>Application</ConfigurationType>
38
+ <UseDebugLibraries>false</UseDebugLibraries>
39
+ <PlatformToolset>v145</PlatformToolset>
40
+ <WholeProgramOptimization>true</WholeProgramOptimization>
41
+ <CharacterSet>Unicode</CharacterSet>
42
+ </PropertyGroup>
43
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
44
+ <ItemDefinitionGroup>
45
+ <ClCompile>
46
+ <WarningLevel>Level3</WarningLevel>
47
+ <SDLCheck>true</SDLCheck>
48
+ <ConformanceMode>true</ConformanceMode>
49
+ <PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
50
+ </ClCompile>
51
+ <Link>
52
+ <SubSystem>Console</SubSystem>
53
+ <GenerateDebugInformation>true</GenerateDebugInformation>
54
+ </Link>
55
+ </ItemDefinitionGroup>
56
+ <ItemGroup>
57
+ <ClCompile Include="helloworld.c" />
58
+ </ItemGroup>
59
+ <ItemGroup>
60
+ <None Include="packages.config" />
61
+ </ItemGroup>
62
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
63
+ <ImportGroup Label="ExtensionTargets">
64
+ <Import Project="packages\Microsoft.WSL.Containers.2.9.3\build\native\Microsoft.WSL.Containers.targets" Condition="Exists('packages\Microsoft.WSL.Containers.2.9.3\build\native\Microsoft.WSL.Containers.targets')" />
65
+ </ImportGroup>
66
+ <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
67
+ <PropertyGroup>
68
+ <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
69
+ </PropertyGroup>
70
+ <Error Condition="!Exists('packages\Microsoft.WSL.Containers.2.9.3\build\native\Microsoft.WSL.Containers.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.WSL.Containers.2.9.3\build\native\Microsoft.WSL.Containers.targets'))" />
71
+ </Target>
72
+</Project>
\ No newline at end of file
doc/samples/WSLC-HelloWorld/helloworld.c
new
+214
@@ -0,0 +1,214 @@
1
+// WSLC-HelloWorld
2
+//
3
+// The simplest possible WSL Container SDK sample, written in C using the flat
4
+// C API (wslcsdk.h). It starts a lightweight WSL container from a small Linux
5
+// image and runs `echo` inside it, streaming the output back to the Windows
6
+// console.
7
+
8
+#include <winsock2.h>
9
+#include <windows.h>
10
+#include <stdio.h>
11
+#include <objbase.h>
12
+#include "wslcsdk.h"
13
+
14
+#pragma comment(lib, "ole32.lib")
15
+#pragma comment(lib, "wslcsdk.lib")
16
+
17
+static const char* IMAGE_NAME = "alpine:latest";
18
+
19
+// Process exit is signalled here so wmain can wait for it.
20
+static HANDLE g_exitEvent = NULL;
21
+static INT32 g_exitCode = -1;
22
+
23
+static void PrintError(const wchar_t* context, HRESULT hr, PWSTR error)
24
+{
25
+ fwprintf(stderr, L"[wslc] Error: %s (0x%08X)", context, hr);
26
+ if (error != NULL)
27
+ {
28
+ fwprintf(stderr, L": %s", error);
29
+ CoTaskMemFree(error);
30
+ }
31
+ fwprintf(stderr, L"\n");
32
+}
33
+
34
+// Forward container stdout/stderr straight to the Windows console.
35
+static void CALLBACK OnStdIO(WslcProcessIOHandle ioHandle, const BYTE* data, uint32_t dataSize, PVOID context)
36
+{
37
+ FILE* output = (ioHandle == WSLC_PROCESS_IO_HANDLE_STDOUT) ? stdout : stderr;
38
+ (void)context;
39
+ fprintf(output, "%.*s", (int)dataSize, (const char*)data);
40
+ fflush(output);
41
+}
42
+
43
+// Record the exit code and wake up wmain.
44
+static void CALLBACK OnProcessExit(INT32 exitCode, PVOID context)
45
+{
46
+ (void)context;
47
+ g_exitCode = exitCode;
48
+ if (!SetEvent(g_exitEvent))
49
+ {
50
+ fwprintf(stderr, L"[wslc] Warning: SetEvent failed (0x%08X)\n", GetLastError());
51
+ }
52
+}
53
+
54
+// Build a storage path in a "WslcStorage" folder next to the executable, so the
55
+// sample doesn't depend on any hard-coded absolute path.
56
+static void GetStoragePath(wchar_t* buffer, size_t count)
57
+{
58
+ wchar_t exePath[MAX_PATH];
59
+ wchar_t* lastSlash;
60
+ GetModuleFileNameW(NULL, exePath, MAX_PATH);
61
+ lastSlash = wcsrchr(exePath, L'\\');
62
+ if (lastSlash != NULL)
63
+ {
64
+ *(lastSlash + 1) = L'\0';
65
+ }
66
+ swprintf(buffer, count, L"%sWslcStorage", exePath);
67
+}
68
+
69
+int wmain(void)
70
+{
71
+ HRESULT hr;
72
+ PWSTR error = NULL;
73
+ int result = 1;
74
+
75
+ WslcSession session = NULL;
76
+ WslcContainer container = NULL;
77
+ WslcProcess process = NULL;
78
+
79
+ WslcSessionSettings sessionSettings;
80
+ WslcContainerSettings containerSettings;
81
+ WslcProcessSettings initProcess;
82
+ WslcProcessSettings execProcess;
83
+ WslcProcessCallbacks callbacks;
84
+ WslcPullImageOptions pullOptions;
85
+ wchar_t storagePath[MAX_PATH];
86
+ DWORD waitResult;
87
+
88
+ PCSTR initArgv[2] = {"/bin/sleep", "60"};
89
+ PCSTR echoArgv[2] = {"/bin/echo", "Hello, World from a WSL container!"};
90
+
91
+ hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
92
+ if (FAILED(hr))
93
+ {
94
+ PrintError(L"Initialize COM", hr, NULL);
95
+ return 1;
96
+ }
97
+
98
+ g_exitEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
99
+ if (g_exitEvent == NULL)
100
+ {
101
+ PrintError(L"Create exit event", HRESULT_FROM_WIN32(GetLastError()), NULL);
102
+ goto cleanup;
103
+ }
104
+
105
+ // ---- Session ----
106
+ fwprintf(stderr, L"[wslc] Creating session...\n");
107
+ GetStoragePath(storagePath, ARRAYSIZE(storagePath));
108
+ hr = WslcInitSessionSettings(L"WSLCHelloWorld", storagePath, &sessionSettings);
109
+ if (FAILED(hr))
110
+ {
111
+ PrintError(L"Init session settings", hr, NULL);
112
+ goto cleanup;
113
+ }
114
+
115
+ hr = WslcCreateSession(&sessionSettings, &session, &error);
116
+ if (FAILED(hr))
117
+ {
118
+ PrintError(L"Create session", hr, error);
119
+ goto cleanup;
120
+ }
121
+
122
+ // ---- Pull image ----
123
+ fwprintf(stderr, L"[wslc] Pulling image '%hs'...\n", IMAGE_NAME);
124
+ ZeroMemory(&pullOptions, sizeof(pullOptions));
125
+ pullOptions.uri = IMAGE_NAME;
126
+ hr = WslcPullSessionImage(session, &pullOptions, &error);
127
+ if (FAILED(hr))
128
+ {
129
+ PrintError(L"Pull image", hr, error);
130
+ goto cleanup;
131
+ }
132
+
133
+ // ---- Create & start container ----
134
+ fwprintf(stderr, L"[wslc] Starting container...\n");
135
+ WslcInitProcessSettings(&initProcess);
136
+ WslcSetProcessSettingsCmdLine(&initProcess, initArgv, 2);
137
+
138
+ WslcInitContainerSettings(IMAGE_NAME, &containerSettings);
139
+ WslcSetContainerSettingsName(&containerSettings, "wslc-helloworld");
140
+ WslcSetContainerSettingsInitProcess(&containerSettings, &initProcess);
141
+ WslcSetContainerSettingsFlags(&containerSettings, WSLC_CONTAINER_FLAG_AUTO_REMOVE);
142
+
143
+ hr = WslcCreateContainer(session, &containerSettings, &container, &error);
144
+ if (FAILED(hr))
145
+ {
146
+ PrintError(L"Create container", hr, error);
147
+ goto cleanup;
148
+ }
149
+
150
+ hr = WslcStartContainer(container, WSLC_CONTAINER_START_FLAG_NONE, &error);
151
+ if (FAILED(hr))
152
+ {
153
+ PrintError(L"Start container", hr, error);
154
+ goto cleanup;
155
+ }
156
+
157
+ // ---- Run echo ----
158
+ fwprintf(stderr, L"[wslc] Running echo...\n");
159
+ WslcInitProcessSettings(&execProcess);
160
+ WslcSetProcessSettingsCmdLine(&execProcess, echoArgv, 2);
161
+
162
+ ZeroMemory(&callbacks, sizeof(callbacks));
163
+ callbacks.onStdOut = OnStdIO;
164
+ callbacks.onStdErr = OnStdIO;
165
+ callbacks.onExit = OnProcessExit;
166
+ WslcSetProcessSettingsCallbacks(&execProcess, &callbacks, NULL);
167
+
168
+ hr = WslcCreateContainerProcess(container, &execProcess, &process, &error);
169
+ if (FAILED(hr))
170
+ {
171
+ PrintError(L"Run echo", hr, error);
172
+ goto cleanup;
173
+ }
174
+
175
+ waitResult = WaitForSingleObject(g_exitEvent, 30000);
176
+ if (waitResult == WAIT_OBJECT_0)
177
+ {
178
+ result = g_exitCode;
179
+ }
180
+ else if (waitResult == WAIT_TIMEOUT)
181
+ {
182
+ fwprintf(stderr, L"[wslc] Error: Timed out waiting for the process to exit.\n");
183
+ }
184
+ else
185
+ {
186
+ PrintError(L"Wait for process exit", HRESULT_FROM_WIN32(GetLastError()), NULL);
187
+ }
188
+
189
+cleanup:
190
+ fwprintf(stderr, L"[wslc] Shutting down...\n");
191
+
192
+ if (process != NULL)
193
+ {
194
+ WslcReleaseProcess(process);
195
+ }
196
+ if (g_exitEvent != NULL)
197
+ {
198
+ CloseHandle(g_exitEvent);
199
+ }
200
+ if (container != NULL)
201
+ {
202
+ WslcStopContainer(container, WSLC_SIGNAL_SIGTERM, 5, NULL);
203
+ WslcReleaseContainer(container);
204
+ }
205
+ if (session != NULL)
206
+ {
207
+ WslcTerminateSession(session);
208
+ WslcReleaseSession(session);
209
+ }
210
+
211
+ fwprintf(stderr, L"[wslc] Done.\n");
212
+ CoUninitialize();
213
+ return result;
214
+}
doc/samples/WSLC-HelloWorld/packages.config
new
+4
@@ -0,0 +1,4 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<packages>
3
+ <package id="Microsoft.WSL.Containers" version="2.9.3" targetFramework="native" />
4
+</packages>
doc/samples/WSLC-Neofetch/README.md
new
+26
@@ -0,0 +1,26 @@
1
+# WSLC-Neofetch
2
+
3
+A minimal sample that uses the [WSL Container API](https://aka.ms/wslc) to run the
4
+Linux `neofetch` command from a native Windows executable. Running `neofetch.exe`
5
+starts a lightweight WSL container, runs `neofetch` inside it, and streams the
6
+output back to your terminal. Command-line arguments are forwarded, so
7
+`neofetch.exe --help` works just like `neofetch --help` on Linux.
8
+
9
+## Build
10
+
11
+Open `WSLCNeofetch.sln` in Visual Studio and build (x64), or from a developer
12
+command prompt:
13
+
14
+```
15
+nuget restore WSLCNeofetch.sln
16
+msbuild WSLCNeofetch.sln /p:Configuration=Debug /p:Platform=x64
17
+```
18
+
19
+## Run
20
+
21
+```
22
+x64\Debug\neofetch.exe # show system info
23
+x64\Debug\neofetch.exe --help # forwarded to neofetch
24
+```
25
+
26
+Progress messages (`[wslc] ...`) are written to stderr, so piping stdout is safe.
doc/samples/WSLC-Neofetch/WSLCNeofetch.sln
new
+30
@@ -0,0 +1,30 @@
1
+Microsoft Visual Studio Solution File, Format Version 12.00
2
+# Visual Studio Version 17
3
+VisualStudioVersion = 17.14.37027.9 d17.14
4
+MinimumVisualStudioVersion = 10.0.40219.1
5
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WSLCNeofetch", "WSLCNeofetch.vcxproj", "{35158918-896E-42B7-B4E8-CF98816259AA}"
6
+EndProject
7
+Global
8
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
9
+ Debug|ARM64 = Debug|ARM64
10
+ Debug|x64 = Debug|x64
11
+ Release|ARM64 = Release|ARM64
12
+ Release|x64 = Release|x64
13
+ EndGlobalSection
14
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
15
+ {35158918-896E-42B7-B4E8-CF98816259AA}.Debug|ARM64.ActiveCfg = Debug|ARM64
16
+ {35158918-896E-42B7-B4E8-CF98816259AA}.Debug|ARM64.Build.0 = Debug|ARM64
17
+ {35158918-896E-42B7-B4E8-CF98816259AA}.Debug|x64.ActiveCfg = Debug|x64
18
+ {35158918-896E-42B7-B4E8-CF98816259AA}.Debug|x64.Build.0 = Debug|x64
19
+ {35158918-896E-42B7-B4E8-CF98816259AA}.Release|ARM64.ActiveCfg = Release|ARM64
20
+ {35158918-896E-42B7-B4E8-CF98816259AA}.Release|ARM64.Build.0 = Release|ARM64
21
+ {35158918-896E-42B7-B4E8-CF98816259AA}.Release|x64.ActiveCfg = Release|x64
22
+ {35158918-896E-42B7-B4E8-CF98816259AA}.Release|x64.Build.0 = Release|x64
23
+ EndGlobalSection
24
+ GlobalSection(SolutionProperties) = preSolution
25
+ HideSolutionNode = FALSE
26
+ EndGlobalSection
27
+ GlobalSection(ExtensibilityGlobals) = postSolution
28
+ SolutionGuid = {FA34E683-A12E-41FE-9821-9474CBF6DD1E}
29
+ EndGlobalSection
30
+EndGlobal
doc/samples/WSLC-Neofetch/WSLCNeofetch.vcxproj
new
+82
@@ -0,0 +1,82 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3
+ <ItemGroup Label="ProjectConfigurations">
4
+ <ProjectConfiguration Include="Debug|x64">
5
+ <Configuration>Debug</Configuration>
6
+ <Platform>x64</Platform>
7
+ </ProjectConfiguration>
8
+ <ProjectConfiguration Include="Release|x64">
9
+ <Configuration>Release</Configuration>
10
+ <Platform>x64</Platform>
11
+ </ProjectConfiguration>
12
+ <ProjectConfiguration Include="Debug|ARM64">
13
+ <Configuration>Debug</Configuration>
14
+ <Platform>ARM64</Platform>
15
+ </ProjectConfiguration>
16
+ <ProjectConfiguration Include="Release|ARM64">
17
+ <Configuration>Release</Configuration>
18
+ <Platform>ARM64</Platform>
19
+ </ProjectConfiguration>
20
+ </ItemGroup>
21
+ <PropertyGroup Label="Globals">
22
+ <VCProjectVersion>17.0</VCProjectVersion>
23
+ <Keyword>Win32Proj</Keyword>
24
+ <ProjectGuid>{35158918-896e-42b7-b4e8-cf98816259aa}</ProjectGuid>
25
+ <RootNamespace>WSLCNeofetch</RootNamespace>
26
+ <TargetName>neofetch</TargetName>
27
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
28
+ <CppWinRTOptimized>true</CppWinRTOptimized>
29
+ <CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
30
+ <AppxPackage>false</AppxPackage>
31
+ </PropertyGroup>
32
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
33
+ <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
34
+ <ConfigurationType>Application</ConfigurationType>
35
+ <UseDebugLibraries>true</UseDebugLibraries>
36
+ <PlatformToolset>v145</PlatformToolset>
37
+ <CharacterSet>Unicode</CharacterSet>
38
+ </PropertyGroup>
39
+ <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
40
+ <ConfigurationType>Application</ConfigurationType>
41
+ <UseDebugLibraries>false</UseDebugLibraries>
42
+ <PlatformToolset>v145</PlatformToolset>
43
+ <WholeProgramOptimization>true</WholeProgramOptimization>
44
+ <CharacterSet>Unicode</CharacterSet>
45
+ </PropertyGroup>
46
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
47
+ <ImportGroup Label="ExtensionSettings">
48
+ <Import Project="packages\Microsoft.Windows.CppWinRT.3.0.260520.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('packages\Microsoft.Windows.CppWinRT.3.0.260520.1\build\native\Microsoft.Windows.CppWinRT.props')" />
49
+ </ImportGroup>
50
+ <PropertyGroup Label="UserMacros" />
51
+ <ItemDefinitionGroup>
52
+ <ClCompile>
53
+ <WarningLevel>Level3</WarningLevel>
54
+ <SDLCheck>true</SDLCheck>
55
+ <ConformanceMode>true</ConformanceMode>
56
+ <LanguageStandard>stdcpp17</LanguageStandard>
57
+ <PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
58
+ </ClCompile>
59
+ <Link>
60
+ <SubSystem>Console</SubSystem>
61
+ <GenerateDebugInformation>true</GenerateDebugInformation>
62
+ </Link>
63
+ </ItemDefinitionGroup>
64
+ <ItemGroup>
65
+ <ClCompile Include="neofetch.cpp" />
66
+ </ItemGroup>
67
+ <ItemGroup>
68
+ <None Include="packages.config" />
69
+ </ItemGroup>
70
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
71
+ <ImportGroup Label="ExtensionTargets">
72
+ <Import Project="packages\Microsoft.Windows.CppWinRT.3.0.260520.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('packages\Microsoft.Windows.CppWinRT.3.0.260520.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
73
+ <Import Project="packages\Microsoft.WSL.Containers.2.9.3\build\native\Microsoft.WSL.Containers.targets" Condition="Exists('packages\Microsoft.WSL.Containers.2.9.3\build\native\Microsoft.WSL.Containers.targets')" />
74
+ </ImportGroup>
75
+ <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
76
+ <PropertyGroup>
77
+ <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
78
+ </PropertyGroup>
79
+ <Error Condition="!Exists('packages\Microsoft.Windows.CppWinRT.3.0.260520.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Windows.CppWinRT.3.0.260520.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
80
+ <Error Condition="!Exists('packages\Microsoft.WSL.Containers.2.9.3\build\native\Microsoft.WSL.Containers.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.WSL.Containers.2.9.3\build\native\Microsoft.WSL.Containers.targets'))" />
81
+ </Target>
82
+</Project>
\ No newline at end of file
doc/samples/WSLC-Neofetch/WSLCNeofetch.vcxproj.filters
new
+25
@@ -0,0 +1,25 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3
+ <ItemGroup>
4
+ <Filter Include="Source Files">
5
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
6
+ <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
7
+ </Filter>
8
+ <Filter Include="Header Files">
9
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
10
+ <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
11
+ </Filter>
12
+ <Filter Include="Resource Files">
13
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
14
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
15
+ </Filter>
16
+ </ItemGroup>
17
+ <ItemGroup>
18
+ <ClCompile Include="neofetch.cpp">
19
+ <Filter>Source Files</Filter>
20
+ </ClCompile>
21
+ </ItemGroup>
22
+ <ItemGroup>
23
+ <None Include="packages.config" />
24
+ </ItemGroup>
25
+</Project>
\ No newline at end of file
doc/samples/WSLC-Neofetch/neofetch.cpp
new
+139
@@ -0,0 +1,139 @@
1
+// WSLC-Neofetch
2
+//
3
+// A Windows executable that wraps the Linux "neofetch" command using the
4
+// WSL Container SDK, written with the modern C++/WinRT projection
5
+// (winrt/Microsoft.WSL.Containers.h) rather than the flat C API.
6
+//
7
+// All command-line arguments are forwarded into the container, so
8
+// "neofetch.exe --help" behaves just like "neofetch --help" on Linux.
9
+
10
+#include <winsock2.h>
11
+#include <windows.h>
12
+#include <stdio.h>
13
+#include <string>
14
+#include <vector>
15
+
16
+#include <winrt/Windows.Foundation.h>
17
+#include <winrt/Windows.Foundation.Collections.h>
18
+#include <winrt/Microsoft.WSL.Containers.h>
19
+
20
+#pragma comment(lib, "windowsapp.lib")
21
+
22
+using namespace winrt;
23
+using namespace winrt::Microsoft::WSL::Containers;
24
+
25
+namespace {
26
+constexpr std::wstring_view c_imageName = L"anrginit/ubuntu-neofetch:1.0";
27
+
28
+// Forward a chunk of container stdout/stderr straight to the Windows console.
29
+void WriteToConsole(FILE* stream, array_view<uint8_t const> data)
30
+{
31
+ fprintf(stream, "%.*s", static_cast<int>(data.size()), reinterpret_cast<const char*>(data.data()));
32
+ fflush(stream);
33
+}
34
+
35
+// Build a storage path in a "WslcStorage" folder next to the executable, so
36
+// the sample doesn't depend on any hard-coded absolute path.
37
+std::wstring GetStoragePath()
38
+{
39
+ wchar_t exePath[MAX_PATH];
40
+ GetModuleFileNameW(nullptr, exePath, MAX_PATH);
41
+ wchar_t* lastSlash = wcsrchr(exePath, L'\\');
42
+ if (lastSlash != nullptr)
43
+ {
44
+ *(lastSlash + 1) = L'\0';
45
+ }
46
+ return std::wstring{exePath} + L"WslcStorage";
47
+}
48
+} // namespace
49
+
50
+int wmain(int argc, wchar_t* argv[])
51
+{
52
+ init_apartment();
53
+
54
+ // argv[0] (our exe) is replaced with "neofetch"; the rest pass through.
55
+ std::vector<hstring> commandLine{L"neofetch"};
56
+ for (int i = 1; i < argc; ++i)
57
+ {
58
+ commandLine.emplace_back(argv[i]);
59
+ }
60
+
61
+ try
62
+ {
63
+ // ---- Session ----
64
+ fwprintf(stderr, L"[wslc] Creating session...\n");
65
+ SessionSettings sessionSettings{L"WSLCNeofetch", GetStoragePath()};
66
+ sessionSettings.CpuCount(4);
67
+ sessionSettings.MemorySizeInMB(2048);
68
+
69
+ Session session{sessionSettings};
70
+ session.Start();
71
+
72
+ // ---- Pull image ----
73
+ fwprintf(stderr, L"[wslc] Pulling image '%ls'...\n", c_imageName.data());
74
+ session.PullImage(PullImageOptions{hstring{c_imageName}});
75
+
76
+ // ---- Create & start container ----
77
+ // The init process keeps the container alive while we exec neofetch.
78
+ fwprintf(stderr, L"[wslc] Starting container...\n");
79
+ ProcessSettings initProcess;
80
+ initProcess.CommandLine(single_threaded_vector<hstring>({L"/bin/sleep", L"60"}));
81
+
82
+ ContainerSettings containerSettings{hstring{c_imageName}};
83
+ containerSettings.Name(L"wslc-neofetch");
84
+ containerSettings.InitProcess(initProcess);
85
+ containerSettings.EnableAutoRemove(true);
86
+
87
+ Container container = session.CreateContainer(containerSettings);
88
+ container.Start();
89
+
90
+ // ---- Exec neofetch ----
91
+ fwprintf(stderr, L"[wslc] Running neofetch...\n");
92
+ ProcessSettings processSettings;
93
+ processSettings.CommandLine(single_threaded_vector<hstring>(std::move(commandLine)));
94
+ processSettings.OutputMode(ProcessOutputMode::Event);
95
+
96
+ Process process = container.CreateProcess(processSettings);
97
+
98
+ handle exitEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)};
99
+ if (!exitEvent)
100
+ {
101
+ throw_last_error();
102
+ }
103
+ int32_t exitCode = -1;
104
+
105
+ process.OutputReceived([](array_view<uint8_t const> data) { WriteToConsole(stdout, data); });
106
+ process.ErrorReceived([](array_view<uint8_t const> data) { WriteToConsole(stderr, data); });
107
+ process.Exited([&](int32_t code) {
108
+ exitCode = code;
109
+ if (!SetEvent(exitEvent.get()))
110
+ {
111
+ fwprintf(stderr, L"[wslc] Warning: SetEvent failed (0x%08X)\n", GetLastError());
112
+ }
113
+ });
114
+
115
+ process.Start();
116
+ DWORD waitResult = WaitForSingleObject(exitEvent.get(), 30000);
117
+ if (waitResult == WAIT_TIMEOUT)
118
+ {
119
+ fwprintf(stderr, L"[wslc] Error: Timed out waiting for the process to exit.\n");
120
+ }
121
+ else if (waitResult != WAIT_OBJECT_0)
122
+ {
123
+ throw_last_error();
124
+ }
125
+
126
+ // ---- Cleanup ----
127
+ fwprintf(stderr, L"[wslc] Shutting down...\n");
128
+ container.Stop(Signal::SIGTERM, std::chrono::seconds{5});
129
+ session.Terminate();
130
+
131
+ fwprintf(stderr, L"[wslc] Done.\n");
132
+ return exitCode;
133
+ }
134
+ catch (hresult_error const& ex)
135
+ {
136
+ fwprintf(stderr, L"[wslc] Error: %ls (0x%08X)\n", ex.message().c_str(), static_cast<uint32_t>(ex.code()));
137
+ return 1;
138
+ }
139
+}
doc/samples/WSLC-Neofetch/packages.config
new
+5
@@ -0,0 +1,5 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<packages>
3
+ <package id="Microsoft.Windows.CppWinRT" version="3.0.260520.1" targetFramework="native" />
4
+ <package id="Microsoft.WSL.Containers" version="2.9.3" targetFramework="native" />
5
+</packages>
doc/samples/WSLC-NextCloud/Program.cs
new
+129
@@ -0,0 +1,129 @@
1
+// WSLC-NextCloud
2
+//
3
+// A Windows console application that runs a Nextcloud server using the WSL
4
+// Container SDK, written in modern C# with the C#/WinRT projection
5
+// (Microsoft.WSL.Containers). The container exposes Nextcloud on
6
+// http://localhost:8080 with persistent data stored next to the executable.
7
+
8
+using Microsoft.WSL.Containers;
9
+
10
+const string imageName = "nextcloud:latest";
11
+
12
+// Storage lives beside the executable — no hard-coded absolute paths. The
13
+// session storage directory must be empty for the session to be created (the
14
+// SDK creates and reuses its own VHD inside it), so the persistent Nextcloud
15
+// data volume lives in a separate sibling directory.
16
+string baseDir = AppContext.BaseDirectory;
17
+string sessionPath = Path.Combine(baseDir, "WslcNextcloudStorage");
18
+string volumePath = Path.Combine(baseDir, "WslcNextcloudData");
19
+Directory.CreateDirectory(volumePath);
20
+
21
+int exitCode = 1;
22
+using var stopEvent = new ManualResetEventSlim(false);
23
+var consoleLock = new object();
24
+using Stream stdout = Console.OpenStandardOutput();
25
+using Stream stderr = Console.OpenStandardError();
26
+
27
+void Write(Stream target, byte[] data)
28
+{
29
+ lock (consoleLock)
30
+ {
31
+ target.Write(data, 0, data.Length);
32
+ target.Flush();
33
+ }
34
+}
35
+
36
+try
37
+{
38
+ // ---- Session ----
39
+ Console.Error.WriteLine("[wslc] Creating session...");
40
+ var sessionSettings = new SessionSettings("WSLCNextCloud", sessionPath)
41
+ {
42
+ CpuCount = 4,
43
+ MemorySizeInMB = 4096,
44
+ // Nextcloud image is ~1.5 GB; use a 10 GB dynamic VHD.
45
+ VhdRequirements = new VhdOptions(string.Empty, 10UL * 1024 * 1024 * 1024, VhdType.Dynamic),
46
+ };
47
+
48
+ using var session = new Session(sessionSettings);
49
+ session.Start();
50
+
51
+ // ---- Pull image ----
52
+ Console.Error.WriteLine($"[wslc] Pulling image '{imageName}' (this may take several minutes)...");
53
+ session.PullImage(new PullImageOptions(imageName));
54
+
55
+ // ---- Create & start container ----
56
+ Console.Error.WriteLine("[wslc] Starting container...");
57
+
58
+ // The init process keeps the container alive while we exec the entrypoint.
59
+ var initProcess = new ProcessSettings
60
+ {
61
+ CommandLine = new List<string> { "/bin/sleep", "infinity" },
62
+ };
63
+
64
+ var containerSettings = new ContainerSettings(imageName)
65
+ {
66
+ InitProcess = initProcess,
67
+ EnableAutoRemove = true,
68
+ NetworkingMode = ContainerNetworkingMode.Bridged,
69
+ // Port mapping: host 8080 -> container 80.
70
+ PortMappings = new List<ContainerPortMapping> { new(8080, 80, PortProtocol.TCP) },
71
+ // Persistent data volume: bind-mount only the data directory, not the
72
+ // entire webroot. Mounting /var/www/html over 9P is extremely slow
73
+ // because Nextcloud writes thousands of PHP files there during init.
74
+ Volumes = new List<ContainerVolume> { new(volumePath, "/var/www/html/data", false) },
75
+ };
76
+
77
+ using var container = session.CreateContainer(containerSettings);
78
+ container.Start();
79
+
80
+ // ---- Exec the Nextcloud entrypoint ----
81
+ Console.Error.WriteLine("[wslc] Launching Nextcloud entrypoint...");
82
+ var processSettings = new ProcessSettings
83
+ {
84
+ CommandLine = new List<string> { "/entrypoint.sh", "apache2-foreground" },
85
+ OutputMode = ProcessOutputMode.Event,
86
+ };
87
+
88
+ using var process = container.CreateProcess(processSettings);
89
+ process.OutputReceived += data => Write(stdout, data);
90
+ process.ErrorReceived += data => Write(stderr, data);
91
+ process.Exited += code =>
92
+ {
93
+ exitCode = code;
94
+ stopEvent.Set();
95
+ };
96
+
97
+ process.Start();
98
+
99
+ Console.Error.WriteLine();
100
+ Console.Error.WriteLine("[wslc] Nextcloud is running at http://localhost:8080");
101
+ Console.Error.WriteLine("[wslc] Press Enter to stop...");
102
+ Console.Error.WriteLine();
103
+
104
+ // Stop when the user presses Enter (or the entrypoint exits on its own).
105
+ var inputThread = new Thread(() =>
106
+ {
107
+ Console.ReadLine();
108
+ if (!stopEvent.IsSet)
109
+ {
110
+ exitCode = 0;
111
+ stopEvent.Set();
112
+ }
113
+ })
114
+ { IsBackground = true };
115
+ inputThread.Start();
116
+
117
+ stopEvent.Wait();
118
+
119
+ Console.Error.WriteLine("[wslc] Shutting down...");
120
+ container.Stop(Signal.SIGTERM, TimeSpan.FromSeconds(10));
121
+ session.Terminate();
122
+ Console.Error.WriteLine("[wslc] Done.");
123
+}
124
+catch (Exception ex)
125
+{
126
+ Console.Error.WriteLine($"[wslc] Error: {ex.Message}");
127
+}
128
+
129
+return exitCode;
doc/samples/WSLC-NextCloud/README.md
new
+31
@@ -0,0 +1,31 @@
1
+# WSLC-NextCloud
2
+
3
+A sample that uses the [WSL Container API](https://aka.ms/wslc) to run a
4
+**Nextcloud** server from a native Windows executable, written in modern C# with
5
+the `Microsoft.WSL.Containers` C#/WinRT projection. Running `nextcloud.exe`
6
+starts a lightweight WSL container, pulls the official `nextcloud` image, and
7
+serves it on **http://localhost:8080** (host port 8080 → container port 80).
8
+
9
+## Build
10
+
11
+Requires the .NET 8 SDK. From this folder:
12
+
13
+```
14
+dotnet build -c Debug
15
+```
16
+
17
+## Run
18
+
19
+```
20
+dotnet run -c Debug
21
+```
22
+
23
+Open **http://localhost:8080** in your browser, then press **Enter** in the
24
+terminal to stop the server and clean up. The first run pulls a ~1.5 GB image
25
+and may take several minutes.
26
+
27
+## Storage
28
+
29
+Two folders are created next to the executable (no absolute paths):
30
+`WslcNextcloudStorage\` holds the ephemeral session VHD, and `WslcNextcloudData\`
31
+is bind-mounted at `/var/www/html/data` so user data persists between runs.
doc/samples/WSLC-NextCloud/WSLCNextCloud.csproj
new
+21
@@ -0,0 +1,21 @@
1
+<Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <OutputType>Exe</OutputType>
5
+ <TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
6
+ <Nullable>enable</Nullable>
7
+ <ImplicitUsings>enable</ImplicitUsings>
8
+ <RootNamespace>WSLCNextCloud</RootNamespace>
9
+ <AssemblyName>nextcloud</AssemblyName>
10
+ <!-- The native wslcsdk.dll shipped by the SDK package is architecture
11
+ specific, and the package picks which copy to deploy from PlatformTarget.
12
+ Default to x64 so a plain `dotnet build` / `dotnet run` works out of the
13
+ box. On an ARM64 device build with `dotnet build -p:PlatformTarget=ARM64`. -->
14
+ <PlatformTarget Condition="'$(PlatformTarget)' == ''">x64</PlatformTarget>
15
+ </PropertyGroup>
16
+
17
+ <ItemGroup>
18
+ <PackageReference Include="Microsoft.WSL.Containers" Version="2.9.3" />
19
+ </ItemGroup>
20
+
21
+</Project>