Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
162434 stories
·
33 followers

Building a 2D Game with WinUI and Win2D

1 Share

For a while I've been working building a little collection of classic arcade games for Windows to better understand Win2D and game development, and to see what is possible with WinUI.. You can see the games in action at games.xaml.dev or grab it right from the store. In this post, I'll break down how the rendering comes together and build a small example: a keyboard-controlled ship flying over a layered starfield.

You really only need very little graphics infrastructure to get started. A few lines, circles, translucent colors, and a managing some drawing orders can take you a long way.

Bringing XAML and immediate-mode rendering together

Space Arcade is a WinUI application. For this tutorial, we'll combine two systems to build a similar little interactive app:

System What it does
XAML Layout, menus, buttons, score displays, and other normal application UI
Win2D Draws the playfield: backgrounds, ships, terrain, projectiles, and effects


Win2D
is a hardware-accelerated 2D drawing API built on Direct2D. Instead of creating a XAML element for every projectile or star, I draw them into a CanvasControl.

That is an immediate-mode approach: each draw callback describes the scene again. The application keeps the world state, but it doesn't keep a separate XAML visual for every object. Immediate-mode rendering is very typical in game development where you have very frequent changes to what is on the screen.

XAML, by contrast, is retained-mode UI. A score label remains a label; a button remains a button and you don't have to manage redrawing or updating it after declaring it. It keeps its layout, keyboard navigation, and accessibility behavior without my renderer having to recreate those features.

The beauty is that these systems can coexist in the same Grid:

<Grid>
    <canvas:CanvasControl Draw="OnDraw" />
    <StackPanel Margin="24" IsHitTestVisible="False">
        <TextBlock Text="SCORE" />
        <TextBlock Text="000000" />
    </StackPanel>
</Grid>

Here, canvas maps to using:Microsoft.Graphics.Canvas.UI.Xaml. Children declared after appear above earlier children, so the text sits above the playfield. Since this is a decorative overlay, I use IsHitTestVisible="False" so it doesn't intercept pointer input in case I want to use the mouse to interact with the game area. However real buttons should remain interactive, so don't disable hit tests on those or the user can't click them.

The important lesson here is: not everything animated on screen needs to become part of the game renderer. Use XAML where it makes sense.

The other separation: input, update, and draw

Inside the game, there are three responsibilities:

Keyboard events ----> held-key state
                            |
Rendering notification ---> update scene using elapsed time
                            |
                            +---> request canvas redraw
                                       |
Canvas Draw callback ----------------> draw current scene

XAML controls remain above the canvas.

The XAML page owns input and lifecycle. A scene or world owns positions, movement, and effects. The renderer reads that state and converts it into drawing commands, and we update that state every frame and based on user-inputs. Some of the drawing code lives directly in its page; more involved rendering can live in a dedicated renderer.

For the tutorial, we'll use three small pieces:

File Responsibility
GamePage.xaml and .xaml.cs Canvas hosting, frame timing, keyboard input, pause, and cleanup
Scene.cs Ship movement, star positions, and particle lifetimes - ie. the game state.
SceneRenderer.cs Drawing, transforms, colors, and layer order


The complete sample project (ZIP) accompanies this post. The snippets below introduce the pieces in order; they are excerpts or intermediate versions, not extra code to paste on top of the final sample.

1. Start with one shape

In a WinUI project with the Microsoft.Graphics.Win2D package installed, a CanvasControl gives us a CanvasDrawingSession in its Draw event that is called at some point after a draw has been requested.

The first renderer can be this small:

private void OnDraw(CanvasControl sender, CanvasDrawEventArgs e)
{
    CanvasDrawingSession ds = e.DrawingSession;
    ds.Clear(Color.FromArgb(255, 5, 11, 25));
    ds.FillCircle(200, 150, 12, Color.FromArgb(255, 140, 239, 242));
}

Those types come from Microsoft.Graphics.Canvas, Microsoft.Graphics.Canvas.UI.Xaml, and Windows.UI.

Clear the canvas, then draw a circle. Pretty basic:

A single cyan circle at control coordinates 200, 150 on a dark canvas.

2. Choose a coordinate system before adding movement

Drawing directly in the control's coordinates is fine for the first dot. For a game, I find it easier to choose a fixed logical playfield.

Our sample uses 960 by 540 logical units, independent of the window size:

ds.Clear(Color.FromArgb(255, 5, 11, 25));

float scale = MathF.Min(ActualWidth / Scene.Width, ActualHeight / Scene.Height);
Vector2 offset = new(
    (width - Scene.Width * scale) / 2,
    (height - Scene.Height * scale) / 2); // Center

ds.Transform =
    Matrix3x2.CreateScale(scale) *
    Matrix3x2.CreateTranslation(offset);

ds.FillCircle(Scene.Width / 2, Scene.Height / 2, 12,
    Color.FromArgb(255, 140, 239, 242));
width and height are the canvas's ActualWidth and ActualHeight, converted to float.

The transform maps the fixed 960 × 540 game coordinates onto the actual canvas. The circle's position (480, 270) is the center of the logical world, not necessarily the center of the control.

For example, on a 1200 x 800 canvas:

  • Scale is 1.25, making the playfield 1200 x 675.
  • Translation adds 62.5 units of padding at the top.
  • The circle's center becomes (600, 400) -the canvas center- and its radius scales from 12 to 15.

Without the transform, it would remain at (480, 270) with radius 12, regardless of window size.

For just one centered circle, we could calculate its screen coordinates directly. The transform becomes useful when drawing an entire scene: every shape, position, and stroke scales together, while movement and gameplay stay in the same logical coordinate system.

Using the smaller scale preserves the aspect ratio. The remaining space becomes letterboxing. A ship at (480, 270) stays in the center, and its movement speed stays in logical units per second. If you don't want letterboxing, that's fine too, but requires more testing to ensure what you want on the screen doesn't get cropped.

Note that the control dimensions are device-independent pixels, not physical monitor pixels. Win2D handles the control's DPI so we don't have to; we aren't multiplying the scale by the display's DPI again.

Clear the full canvas before applying the world transform. The final sample also clips drawing to the logical playfield using CreateLayer, so particles and glow cannot spill into the letterbox margins. Restore the previous transform afterward.

If you later support aiming with the mouse, invert this same transform to convert pointer positions back into world coordinates. Don't maintain a second, slightly different scaling calculation for input.

The cyan circle placed at the center of the 960 by 540 logical playfield.

3. Make the dot move

The ship's position belongs to the scene stored as a 2D vector. For example:

public Vector2 Player { get; private set; } = new(Width / 2, Height / 2);

Key-down and key-up events maintain a set of held keys. On each update, the page converts that set into a direction:

Vector2 direction = new(
    (Held(VirtualKey.Right, VirtualKey.D) ? 1 : 0) -
    (Held(VirtualKey.Left, VirtualKey.A) ? 1 : 0),
    (Held(VirtualKey.Down, VirtualKey.S) ? 1 : 0) -
    (Held(VirtualKey.Up, VirtualKey.W) ? 1 : 0));

The scene then applies movement and multiplies it by the amount of time passed since the last frame (dt) to make the speed constant:

if (direction.LengthSquared() > 1)
    direction = Vector2.Normalize(direction);

Velocity = direction * PlayerSpeed;
Player = Vector2.Clamp(
    Player + Velocity * dt,
    new Vector2(PlayerRadius),
    new Vector2(Width - PlayerRadius, Height - PlayerRadius));

Here, PlayerSpeed is 240 logical units per second. Normalizing the diagonal direction stops diagonal movement from being faster than horizontal movement. Opposite keys cancel out.

For timing, the sample uses CompositionTarget.Rendering as a UI-thread frame notification. Although its event argument is declared as object, we can cast it to WinUI's Microsoft.UI.Xaml.Media.RenderingEventArgs and use its RenderingTime timestamp to calculate the time passed since the previous frame:

TimeSpan now = ((RenderingEventArgs)e).RenderingTime;
if (_lastFrame is not TimeSpan previous)
{
    _lastFrame = now;
    return;
}
float dt = (float)Math.Clamp((now - previous).TotalSeconds, 0, 0.05);
_lastFrame = now;

if (_paused) return;
_scene.Update(dt, direction);
_canvas.Invalidate();

RenderingTime is a frame timestamp, not the duration of the current frame. Subtract the previous timestamp to get elapsed time. _lastFrame is a nullable TimeSpan? field, initially null; the first notification establishes a baseline without advancing the scene. Reset it to null when the page loads or pause changes so resuming doesn't include time spent paused.

The page calls Invalidate() to request drawing; that isn't a synchronous call to Draw. Requests can be coalesced and called from multiple events within a single frame and still only trigger a single draw.

The 50 ms cap is a simple defense against a giant movement jump after a stall. It deliberately discards excess elapsed time. This is a variable-step tutorial, not a deterministic physics engine (For a collision-heavy simulation, consider a fixed-step accumulator with bounded catch-up work).

Win2D also has CanvasAnimatedControl, with its own animation loop and game-loop-thread model. This example uses CanvasControl to keep input, simulation, drawing, and XAML updates on the same UI thread. That's a simplicity tradeoff, not a claim that it's inherently better for games: a busy UI thread also delays the game. CanvasAnimatedControl can isolate the game loop from that work, but requires safely transferring input and UI state between threads.

Step 3: just the dot moving, cropped around its circular path.

The clips in steps 3-5 use scripted directions through the same Scene.Update method, with Win2D-rendered frames encoded at 30 frames per second. Each shows only the drawing introduced so far, cropped to the movement area without magnification.

4. Replace the dot with a tiny ship

Instead of a circle, let's make a more recognizable character to look like a simple spaceship. We'll draw a triangle in local coordinates:

Vector2 nose = new(16, 0);
Vector2 upper = new(-11, -10);
Vector2 lower = new(-11, 10);

ds.DrawLine(nose, upper, Cyan, 1.5f);
ds.DrawLine(upper, lower, Cyan, 1.5f);
ds.DrawLine(lower, nose, Cyan, 1.5f);

These points describe a ship centered near the origin, facing right. To draw it at the player's position, compose its rotation and translation with the existing world-to-canvas transform:

Matrix3x2 previous = ds.Transform;
ds.Transform =
    Matrix3x2.CreateRotation(scene.Heading) *
    Matrix3x2.CreateTranslation(scene.Player) *
    previous;

In System.Numerics, that order applies local rotation first, then placement in the world, then the world-to-canvas transform. Reversing it can rotate the ship's position around the origin instead of rotating the ship itself.

While moving, the scene updates Heading using MathF.Atan2(Velocity.Y, Velocity.X). When movement stops, it keeps the last heading.

Restore the previous transform in a finally block after drawing the ship. Transforms are shared drawing-session state: forgetting to restore one can make the next planet or particle rotate with the player.

Step 4: the same movement now drives a small, direction-facing ship. No starfield or glow yet.

5. Build a starfield that feels like several layers

Create collecgion of stars once, each with a position, depth, and twinkle phase. The sample uses 150 stars split between three depths:

float depth = (i % 3) switch
{
    0 => 0.25f,
    1 => 0.6f,
    _ => 1
};

Randomize the initial positions and phases, but don't regenerate them in Draw. Otherwise, stars teleport between frames and the background becomes visual noise.

During updates, move them at different speeds:

Vector2 drift = new Vector2(-18, 6) - Velocity * 0.15f;
star.Position += drift * star.Depth * dt;

The constant drift keeps the background alive while the ship is stationary. The velocity term nudges it opposite the ship's movement. Larger depth values move farther, creating a simple parallax cue simulating different distances to the stars.

We'll also wrap stars when they leave the playfield, so the enter on the other side:

private static float Wrap(float value, float extent) =>
    (value % extent + extent) % extent;

C#'s remainder can be negative, so the extra normalization matters for stars moving left or up.

In the renderer, we can also vary brightness smoothly to give a twinkling effect:

float twinkle = 0.7f + 0.3f * (float)Math.Sin(
    scene.Time * (1 + star.Depth) + star.Phase);
byte alpha = (byte)(twinkle * (60 + 150 * star.Depth));

ds.FillCircle(
    star.Position,
    0.5f + star.Depth,
    Color.FromArgb(alpha, 185, 220, 245));

Depth now controls speed, size, and brightness. Each is a small signal; together they make a flat collection of dots feel more spatial.

Twinkle is a function of scene time. Drawing the same state twice produces the same appearance instead of advancing an animation twice.

Step 5: stars drift at different speeds and change brightness smoothly while the ship moves.

6. Give drawing order a name

There doesn't have to be a layer object or a separate render target for every visual layer. But it can be useful to create a method that renders each object to more logically understand the draw order.

DrawHaze(ds);
DrawStars(ds, scene);
DrawPlanet(ds);
DrawParticles(ds, scene);
DrawPlayer(ds, scene);
DrawForeground(ds);

Our haze is a handful of large, faint circles. The planet is an opaque disk with a thin illuminated outline and an elliptical ring. Its opaque body covers stars drawn behind it. The exhaust is behind the ship (see step 8), and faint scanlines are drawn last.

The XAML heading and instructions sit above all of these Win2D passes. We don't redraw that text in the canvas.

Step 6: drawing order adds haze, an opaque planet, and a foreground treatment, with XAML text above the canvas. The particle pass is still empty; we'll fill it in step 8.

7. Adding a simple neon-like glow effect

Much of Space Arcade's neon appearance comes from drawing the same primitive several times with slightly different tints:

private static void GlowLine(
    CanvasDrawingSession ds, Vector2 a, Vector2 b, Color color)
{
    ds.DrawLine(a, b, Tint(color, 0.06f), 11);
    ds.DrawLine(a, b, Tint(color, 0.2f), 5);
    ds.DrawLine(a, b, color, 1.5f);
}

Tint returns the same RGB color with an alpha derived from the supplied opacity.

The broad, faint stroke suggests scattered light. The narrower stroke builds a halo. The crisp center preserves the shape. Replacing the ship's three ordinary lines with GlowLine immediately changes its character to have a slight glow.

This is an approximation of glow, not a blur, bloom shader, or simulation of emitted light. The sample uses normal alpha compositing, not actual additive blending.

For small line-based scenes, the approximation is simple and effective. It also has a cost: three strokes instead of one, with overlapping translucent pixels. More glow isn't automatically better.

If an effect really needs a blurred silhouette, Win2D supports offscreen render targets and effects such as GaussianBlurEffect. That adds resource management, intermediate surfaces, and fill cost, and wasn't something I really needed.

Step 7, shown close-up: broad translucent strokes add a halo without replacing the crisp ship outline. This crop is magnified 2x to make the layered strokes easier to see, but the effect is still quite subtle.

8. Add a particle layer

Particles are another small simulation that we can use for exhaust that we can define with a record:

internal record struct Particle(
    Vector2 Position, Vector2 Velocity, float Life);

During updates, reduce their remaining life and advance their position. Remove expired particles while iterating backward through the list.

While the ship is moving, the sample emits exhaust behind its local forward direction at 45 particles per second. An elapsed-time accumulator determines how many to create, rather than emitting one particle per frame. This keeps the intended emission rate independent of the display's refresh rate.

Drawing is simpler than updating:

float fade = Math.Clamp(particle.Life / Particle.Lifetime, 0, 1);
ds.FillCircle(particle.Position, 5 * fade, Tint(Amber, 0.1f * fade));
ds.FillCircle(particle.Position, 1.8f * fade, Tint(Amber, fade));

Again, one faint halo and one brighter center. Shrinking and fading together makes the exhaust dissipate without any texture animation.

The tutorial emits from the latest ship position. For extremely fast motion or longer frame gaps, distributing emissions along the traveled segment produces a smoother trail.

Step 8: time-based emission adds a fading exhaust trail behind the ship, completing the sample.

The less glamorous part: focus and lifetime

A renderer that looks good for ten seconds isn't enough. It has to behave when you click, resize, switch apps, and leave the page.

The complete sample handles the essentials:

  • On Loaded, create the canvas, subscribe to drawing and frame notifications, reset the frame-time baseline, and focus the page.
  • On Unloaded, unsubscribe from frame and window events, detach drawing, and call RemoveFromVisualTree() on the canvas. A later load creates a new canvas.
  • On focus loss, clear held keys so a missed key-up event doesn't leave the ship moving.
  • On window deactivation, pause and clear input. Returning to the window does not resume automatically; The user can press P to unpause and resume the game.

CompositionTarget.Rendering is a static event. Leaving a page subscribed can keep it alive and updating after it is no longer visible. Treat subscription and cleanup as a pair. This event in general you should be careful with and always unsubscribe once you don't need it, as it causes the WinUI rendering loop to constantly run with no pauses.

Download the complete example

Download the sample source (ZIP), extract it, and open RenderingSample.csproj. The archive includes the complete packaged WinUI project, package references, manifest, and tutorial source; there's no need to scaffold a project or copy files manually.

You'll need Windows, the .NET 10 SDK, and Developer Mode. The included README has the run command for WinApp CLI 0.7 or later; you can also use Visual Studio and just launch it from there. Try moving diagonally, releasing the keys, pausing, resizing the window, and switching to another app.

How the same approach grows into different games

Once the frame timing, coordinate system, and drawing passes are in place, the visual vocabulary can change without replacing the whole application.

A scrolling terrain game adds a camera offset and draws terrain segments before ships and explosions. A circular arena builds shapes from angles and radii. A perspective-looking playfield can project logical lane and depth coordinates into 2D points before sending them to Win2D.

That last distinction is useful: a scene can look three-dimensional without being rendered by a full 3D engine. In Space Arcade, projected geometry can still be drawn as ordinary 2D lines. Logical positions remain the source of truth for gameplay.

The first useful renderer can be remarkably modest: one canvas, one scene, elapsed-time updates, and a handful of well-ordered drawing methods. The rest is merely choosing what to put in each layer.

Full demo

I would love for you to try out my game from the store now (it has a free fully playable trial!), and please leave a review in the store if you like it.

Further reading

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Mass Surveillance and the Loss of Fun

1 Share

In this episode of Future Knowledge, Alexis Durante-Tierney and Helen Vogelsong-Donahue, hosts of the Born Digital podcast, explore how growing up under constant surveillance may be changing the way young people socialize, experiment, and have fun. From smartphones and social media to school surveillance and cameras in public spaces, they ask what happens when there’s no expectation of privacy—or forgetting. The conversation also considers what mass recording and preservation mean for libraries and archives, and for our ability to let the past disappear.

This conversation is based on the Born Digital essay, "How Fun Became Dystopian: On Gen Z, Flock cameras, and the death of fun," by Helen Vogelsong-Donahue, Alexis Durante-Tierney, and born digital on Substack. https://b0rndigital.substack.com/p/how-fun-became-dystopian

This conversation was recorded on 9/22/2026.

Check out all of the Future Knowledge episodes at https://archive.org/details/future-knowledge





Download audio: https://media.transistor.fm/066556db/1c8ec698.mp3
Read the whole story
alvinashcraft
2 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Before You Push or Deploy: Automate Nine .NET Project Checks with the Spargine Dev Tool

1 Share
Before releasing code to production, it's crucial to ensure a successful build and check for issues like stale references and inconsistent package versions. The Spargine Global Dev Tool automates these checks through nine PowerShell commands, generating reports to assist developers in maintaining quality and addressing potential problems before deployment.
Read the whole story
alvinashcraft
2 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Getting started with DSC 3.0 – Part 4: Configuring IIS with multiple resources and dependsOn

1 Share

Until now we configured one resource at a time. A real server needs several, and they depend on each other. You can't create a website before IIS is installed, and you can't attach a web application to a website that doesn't exist yet.

In this post we set up IIS, an application pool, a website and a web application in a single configuration document, and use dependsOn to control the order.

Note: this post is part of a bigger series on Microsoft Desired State Configuration.

What we are building

Six resource instances, with these dependencies:

  • IIS (Windows feature): no dependencies
  • Site folder and App folder: no dependencies
  • Demo app pool: needs IIS
  • Demo site: needs the app pool and the site folder
  • Demo application: needs the site, the app pool and the app folder

What you need

  • DSC v3.2 or later. The directives syntax used below was introduced in 3.2
  • Windows Server, because the WindowsFeature resource needs the Server Manager module
  • The WebAdministrationDsc module, installed for Windows PowerShell

Run this in a Windows PowerShell (not PowerShell 7) session:

Install-Module -Name WebAdministrationDsc -Scope AllUsers

Remark: the Microsoft.Adapter/WindowsPowerShell adapter runs PSDSC resources in Windows PowerShell (powershell.exe), so the module must be available there. You can check what the adapter discovers with dsc resource list --adapter Microsoft.Adapter/WindowsPowerShell.

The configuration document

Save this as iis.dsc.yaml:

$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
directives:
  securityContext: elevated
resources:
- name: IIS
  type: PSDesiredStateConfiguration/WindowsFeature
  directives:
    requireAdapter: Microsoft.Adapter/WindowsPowerShell
  properties:
    Name: Web-Server
    Ensure: Present
- name: Site folder
  type: PSDesiredStateConfiguration/File
  directives:
    requireAdapter: Microsoft.Adapter/WindowsPowerShell
  properties:
    DestinationPath: C:\inetpub\demo\site
    Type: Directory
    Ensure: Present
- name: App folder
  type: PSDesiredStateConfiguration/File
  directives:
    requireAdapter: Microsoft.Adapter/WindowsPowerShell
  properties:
    DestinationPath: C:\inetpub\demo\app
    Type: Directory
    Ensure: Present
- name: Demo app pool
  type: WebAdministrationDsc/WebAppPool
  directives:
    requireAdapter: Microsoft.Adapter/WindowsPowerShell
  properties:
    Name: DemoAppPool
    Ensure: Present
    State: Started
    managedRuntimeVersion: v4.0
  dependsOn:
  - "[resourceId('PSDesiredStateConfiguration/WindowsFeature', 'IIS')]"
- name: Demo site
  type: WebAdministrationDsc/WebSite
  directives:
    requireAdapter: Microsoft.Adapter/WindowsPowerShell
  properties:
    Name: DemoSite
    Ensure: Present
    State: Started
    PhysicalPath: C:\inetpub\demo\site
    ApplicationPool: DemoAppPool
    BindingInfo:
    - Protocol: http
      Port: 8080
  dependsOn:
  - "[resourceId('WebAdministrationDsc/WebAppPool', 'Demo app pool')]"
  - "[resourceId('PSDesiredStateConfiguration/File', 'Site folder')]"
- name: Demo application
  type: WebAdministrationDsc/WebApplication
  directives:
    requireAdapter: Microsoft.Adapter/WindowsPowerShell
  properties:
    Website: DemoSite
    Name: app
    WebAppPool: DemoAppPool
    PhysicalPath: C:\inetpub\demo\app
    Ensure: Present
  dependsOn:
  - "[resourceId('WebAdministrationDsc/WebSite', 'Demo site')]"
  - "[resourceId('WebAdministrationDsc/WebAppPool', 'Demo app pool')]"
  - "[resourceId('PSDesiredStateConfiguration/File', 'App folder')]"

Quite a bit of YAML, so let's look at the important parts.

How dependsOn works

If you used PowerShell DSC before, you know this syntax:

DependsOn = '[WindowsFeature]IIS'

That's gone. In DSC 3.0 a dependency is a resourceId() call with two arguments: the resource type and the instance name:

dependsOn:
- "[resourceId('PSDesiredStateConfiguration/WindowsFeature', 'IIS')]"

A few things to keep in mind:

  • dependsOn takes a list, so one resource can depend on several others. The web application waits for the site, the app pool and the app folder.
  • The instance name is the name of the instance in the document (IIS), not a property like Name inside properties.
  • The order in the document doesn't matter. DSC sorts by dependencies. I listed everything top-down for readability, but you can move the web application to the top and get the same result.

Remark: with the old adapter you had to nest the PSDSC resources inside the adapter instance, and dependsOn only worked between neighbors inside it. With requireAdapter every instance is top-level, so dependencies just work across the document.

The other directives

Two directives in the document are worth a short explanation:

  • requireAdapter on each instance tells DSC which adapter runs the resource. You can skip it, and DSC then uses the first adapter that can run the resource. I prefer being explicit.
  • securityContext: elevated on the document level states that this document needs an elevated session.

The adapters

We use requireAdapter on every instance in this document, so it's worth understanding what it does.

DSC's own resources are self-contained. A resource manifest tells DSC which executable to call and which properties the resource has. DSC can call it directly.

PowerShell DSC resources like WindowsFeature, File and WebSite don't work like that. They are PowerShell modules, with a MOF schema or a PowerShell class, and they only run inside a PowerShell engine. DSC can't call them directly.

An adapter is the bridge. It's a resource whose job is to:

  • Discover the PSDSC resources installed on the machine
  • Tell DSC which properties each of them has
  • Invoke them for the get, set and test operations

You can see them in the resource list, as a separate kind:

dsc resource list

Look at the Kind column. Normal resources show up as Resource, adapters as Adapter.

Which adapters are there?

  • Microsoft.Adapter/WindowsPowerShell: runs PSDSC resources in Windows PowerShell 5.1, on top of the PSDSC 1.1 engine. It supports script-based, MOF-based and class-based resources. Windows only. This is the one we use in this post.
  • Microsoft.Adapter/PowerShell: runs class-based PSDSC resources in PowerShell 7. Works on Windows, Linux and macOS.
  • Microsoft.Windows/WMI: exposes WMI classes as resources.

Remark: before DSC 3.2 the first two were called Microsoft.Windows/WindowsPowerShell and Microsoft.DSC/PowerShell. They still work, but they are deprecated and will be removed in DSC 4.0. If you see the old names somewhere, that's why.

Apply it

Open an elevated terminal and run:

dsc config set --file iis.dsc.yaml

This takes a while on a fresh machine. IIS needs to be installed first, and everything else follows.

Remark: The adapter runs Windows PowerShell DSC resources through the Local Configuration Manager (LCM), which requires a local WinRM connection. If WinRM is disabled, you should enable it first.

To check the result, use appcmd, which comes with IIS:

& "$env:windir\system32\inetsrv\appcmd.exe" list site
& "$env:windir\system32\inetsrv\appcmd.exe" list app
& "$env:windir\system32\inetsrv\appcmd.exe" list apppool

Now you can run the test operation to confirm everything is in the desired state:

dsc config test --file iis.dsc.yaml

Troubleshooting

Using the example above didn't work as I would expect. First, it requires the WebAdministrationDSC module to be installed:

Install-Module -Name WebAdministrationDsc -RequiredVersion 4.2.1

But even after the module got installed, the execution of the configuration failed.

ERROR PID 6452: Script failed with terminating error at line 0 for statement `` - System.Management.Automation.MethodInvocationException: Exception calling "Invoke" with "4" argument(s)

Further investigation showed that the problem was related to the website configuration. It seems that DSC 3.0 at the moment gets into trouble when you have child properties configured like we have in the BindingInfo for the website:

$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
directives:
  securityContext: elevated
resources:
- name: Demo site
  type: WebAdministrationDsc/WebSite
  directives:
    requireAdapter: Microsoft.Adapter/WindowsPowerShell
  properties:
    Name: DemoSite
    Ensure: Present
    State: Started
    PhysicalPath: C:\inetpub\demo\site
    ApplicationPool: DemoAppPool
    BindingInfo:
    - Protocol: http
      Port: 8080
  dependsOn:
  - "[resourceId('WebAdministrationDsc/WebAppPool', 'Demo app pool')]"
  - "[resourceId('PSDesiredStateConfiguration/File', 'Site folder')]"

Removing the BindingInfo fixed the issue but doesn't count for me as a real solution.

As a workaround, I switched to raw powershell scripts to create the sites. I'll add a general troubleshooting post at the end of this series with all the details.

dependsOn is about order, not state

This is an important one. dependsOn only decides in which order DSC processes the resources. It does not check that the dependency is actually in the desired state before moving on.

Remark: if you need a real gate, there is the Microsoft.DSC/Assertion resource. That's for the next post.

More information

Read the whole story
alvinashcraft
3 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Why does the compiler sometimes use ud2 and sometimes int 3 for code that shouldn’t execute?

1 Share

There are two common ways for x86 compilers to indicate that execution should not have reached a particular point: One is the single-byte int 3 breakpoint opcode. And the other is the two-byte ud2 invalid instruction opcode. How do they decide which one to use?

The two types of “bad instructions” are typically for different purposes.

The int 3 means “There is no code here. If you somehow got here, then somebody used an invalid function pointer.” It is used as padding, such as between functions. There is no way that code can reach the int 3 by normal execution. You must have generated an invalid address and called it.

The ud2 is used to mark the case when execution reached something that should be unreachable. It means “You executed a code path that the standard says is undefined behavior.” For example, falling off the end of a non-void function without returning a value, or following the call to a [[noreturn]] function in case it somehow managed to return.

Using int 3 for “there is not even code here” is important because it’s a one-byte instruction. If you had used the two-byte instruction ud2 instruction, then that stray function pointer might land on the second byte of the instruction, in which case it’s not ud2 any more. Instead of stopping immediately, it starts executing garbage code:

0b 0f            or      ecx,dword ptr [edi]
0b 0f            or      ecx,dword ptr [edi]
0b 0f            or      ecx,dword ptr [edi]

Okay, so what does this mean for you?

If you find yourself executing the ud2 instruction, then look for logic flaws in your code. If you find yourself executing the int 3 instruction, then look for an uninitialized function pointer variable, or a hard-coded breakpoint, or a debugger-inserted breakpoint.

The post Why does the compiler sometimes use <CODE>ud2</CODE> and sometimes <CODE>int 3</CODE> for code that shouldn’t execute? appeared first on The Old New Thing.

Read the whole story
alvinashcraft
3 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

v1.0.17-preview.10

1 Share

Internal dependency updates only: this prerelease updates the SDK snapshot for Copilot CLI 1.0.93-4, with no user-visible SDK API changes.

Full Changelog: v1.0.17-preview.9...v1.0.17-preview.10

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • github.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "github.com"

See Network Configuration for more information.

Generated by Release Notes Generator · copilot · auto · 21.9 AIC · ⌖ 6.24 AIC · ⊞ 10.6K

Read the whole story
alvinashcraft
3 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories