Skip to content

Commit

Permalink
Inbox Windows Sandbox Environments extension (microsoft#2752)
Browse files Browse the repository at this point in the history
* Windows Sandbox Extension

* Launch Windows Sandbox

* Tracelogging and Appx Check

* Fix packaging for WSB extension

* Fix appx package check

* Remove any cpu

* Only return Compute System provider if OC is enabled and appx is not installed

* Add publish profiles

* Change compute system display name

* Start and stop Windows Sandbox

* Address PR comments

* Fix race

* Empty Compute System ID

* Set CPU Count

* Set compute system ID.

* Address PR comments

* Locking Windows Sandbox in resources file

* Fix indentation in appx package

* Remove not implemented exceptions

* Set constants for default memory and storage

* Rename app description resource

* Fix packaging

* Fix hang on stopping already stopped Compute System

---------

Co-authored-by: kevinve <[email protected]>
  • Loading branch information
kvega005 and kevinve committed May 23, 2024
1 parent 5aaa89d commit 34b762e
Show file tree
Hide file tree
Showing 24 changed files with 1,374 additions and 1 deletion.
18 changes: 18 additions & 0 deletions DevHome.sln
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.EnvironmentVariable
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DevHome.Telemetry.Native", "telemetry\DevHome.Telemetry.Native\DevHome.Telemetry.Native.vcxproj", "{8EB52F7D-D216-49FF-BF16-DE06E4695950}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WindowsSandboxExtension", "WindowsSandboxExtension", "{4ACF917D-B2CC-4CF2-8EE1-0EBBB52A69F0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsSandboxExtension", "extensions\WindowsSandboxExtension\WindowsSandboxExtension.csproj", "{118E20E8-FD8A-40CF-83A5-F912B9187787}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|arm64 = Debug|arm64
Expand Down Expand Up @@ -714,6 +718,18 @@ Global
{8EB52F7D-D216-49FF-BF16-DE06E4695950}.Release|x64.Build.0 = Release|x64
{8EB52F7D-D216-49FF-BF16-DE06E4695950}.Release|x86.ActiveCfg = Release|Win32
{8EB52F7D-D216-49FF-BF16-DE06E4695950}.Release|x86.Build.0 = Release|Win32
{118E20E8-FD8A-40CF-83A5-F912B9187787}.Debug|arm64.ActiveCfg = Debug|arm64
{118E20E8-FD8A-40CF-83A5-F912B9187787}.Debug|arm64.Build.0 = Debug|arm64
{118E20E8-FD8A-40CF-83A5-F912B9187787}.Debug|x64.ActiveCfg = Debug|x64
{118E20E8-FD8A-40CF-83A5-F912B9187787}.Debug|x64.Build.0 = Debug|x64
{118E20E8-FD8A-40CF-83A5-F912B9187787}.Debug|x86.ActiveCfg = Debug|x86
{118E20E8-FD8A-40CF-83A5-F912B9187787}.Debug|x86.Build.0 = Debug|x86
{118E20E8-FD8A-40CF-83A5-F912B9187787}.Release|arm64.ActiveCfg = Release|arm64
{118E20E8-FD8A-40CF-83A5-F912B9187787}.Release|arm64.Build.0 = Release|arm64
{118E20E8-FD8A-40CF-83A5-F912B9187787}.Release|x64.ActiveCfg = Release|x64
{118E20E8-FD8A-40CF-83A5-F912B9187787}.Release|x64.Build.0 = Release|x64
{118E20E8-FD8A-40CF-83A5-F912B9187787}.Release|x86.ActiveCfg = Release|x86
{118E20E8-FD8A-40CF-83A5-F912B9187787}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -765,6 +781,8 @@ Global
{DB3D0F2C-1A7F-44B4-B408-B21A56212985} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD}
{623998FD-B0A6-4980-95D5-A5072301CA10} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD}
{AF527EA4-6A24-4BD6-BC6E-A5863DC3489C} = {623998FD-B0A6-4980-95D5-A5072301CA10}
{4ACF917D-B2CC-4CF2-8EE1-0EBBB52A69F0} = {DCAF188B-60C3-4EDB-8049-BAA927FBCD7D}
{118E20E8-FD8A-40CF-83A5-F912B9187787} = {4ACF917D-B2CC-4CF2-8EE1-0EBBB52A69F0}
{FAB6FAA7-ADF4-4B65-9831-0C819915E6E1} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD}
{19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7} = {FAB6FAA7-ADF4-4B65-9831-0C819915E6E1}
{1317314E-9BDD-4F1C-A76F-22121637A091} = {FAB6FAA7-ADF4-4B65-9831-0C819915E6E1}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions extensions/WindowsSandboxExtension/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WindowsSandboxExtension;

internal sealed class Constants
{
public const string WindowsSandboxExe = "WindowsSandbox.exe";
public const string ProviderDisplayName = "Windows Sandbox";
public const string ProviderId = "Microsoft.WindowsSandbox";
public const string Thumbnail = "ms-appx:///Assets/windows-sandbox-thumbnail.jpg";

// We use different icon locations for different builds. Note these are ms-resource URIs, but are used by Dev Home to load the providers icon.
// from the extension package. Extensions that implement the IComputeSystemProvider interface must provide a provider icon in this format.
// Dev Home will use SHLoadIndirectString (https://learn.microsoft.com/windows/win32/api/shlwapi/nf-shlwapi-shloadindirectstring) to load the
// location of the icon from the extension package.Once it gets this location, it will load the icon from the path and display it in the UI.
// Icons should be located in an extension resource.pri file which is generated at build time.
// See the MakePri.exe documentation for how you can view what is in the resource.pri file, so you can find the location of your icon.
// https://learn.microsoft.com/windows/uwp/app-resources/makepri-exe-command-options. (use MakePri.exe in a VS Developer Command Prompt or
// Powershell window)
#if CANARY_BUILD
public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome.Canary/Files/Assets/windows-sandbox-icon.png";
#elif STABLE_BUILD
public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome/Files/Assets/windows-sandbox-icon.png";
#else
public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome.Dev/Files/Assets/windows-sandbox-icon.png";
#endif
}
51 changes: 51 additions & 0 deletions extensions/WindowsSandboxExtension/Helpers/DependencyChecker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Management;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using Windows.Management.Deployment;
using WinRT;

namespace WindowsSandboxExtension.Helpers;

internal sealed class DependencyChecker
{
private const string OptionalComponentName = "Containers-DisposableClientVM";
private const string PackageFamilyName = "MicrosoftWindows.WindowsSandbox_cw5n1h2txyewy";

public static bool IsOptionalComponentEnabled()
{
var searcher = new ManagementObjectSearcher($"SELECT InstallState FROM Win32_OptionalFeature WHERE Name = '{OptionalComponentName}'");
var collection = searcher.Get();

foreach (ManagementObject instance in collection)
{
if (instance["InstallState"] != null)
{
var state = Convert.ToInt32(instance.GetPropertyValue("InstallState"), CultureInfo.InvariantCulture);

// 1 means the feature is enabled
return state == 1;
}
}

// Return false if the feature is not found
return false;
}

public static bool IsNewWindowsSandboxExtensionInstalled()
{
PackageManager packageManager = new PackageManager();

var securityId = WindowsIdentity.GetCurrent().Owner?.ToString();
var packages = packageManager.FindPackagesForUser(securityId, PackageFamilyName);

return packages.Any();
}
}
24 changes: 24 additions & 0 deletions extensions/WindowsSandboxExtension/Helpers/Logging.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;

namespace WindowsSandboxExtension.Helpers;

public class Logging
{
public static readonly string LogExtension = ".dhlog";

public static readonly string LogFolderName = "Logs";

public static readonly string DefaultLogFileName = "WindowsSandbox";

private static readonly Lazy<string> _logFolderRoot = new(() => Path.Combine(ApplicationData.Current.TemporaryFolder.Path, LogFolderName));

public static readonly string LogFolderRoot = _logFolderRoot.Value;
}
38 changes: 38 additions & 0 deletions extensions/WindowsSandboxExtension/Helpers/Resources.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Windows.ApplicationModel.Resources;
using Serilog;

namespace WindowsSandboxExtension.Helpers;

internal sealed class Resources
{
private static ResourceLoader? _resourceLoader;

public static string GetResource(string identifier, ILogger? log = null)
{
try
{
if (_resourceLoader == null)
{
var path = ResourceLoader.GetDefaultResourceFilePath();
_resourceLoader = new ResourceLoader(path);
}

return _resourceLoader.GetString(identifier);
}
catch (Exception ex)
{
log?.Error(ex, $"Failed loading resource: {identifier}");

// If we fail, load the original identifier so it is obvious which resource is missing.
return identifier;
}
}
}
1 change: 1 addition & 0 deletions extensions/WindowsSandboxExtension/NativeMethods.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SetForegroundWindow
130 changes: 130 additions & 0 deletions extensions/WindowsSandboxExtension/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Windows.AppLifecycle;
using Microsoft.Windows.DevHome.SDK;
using Serilog;
using Windows.ApplicationModel.Activation;
using WindowsSandboxExtension.Helpers;
using WindowsSandboxExtension.Providers;
using WinRT;

namespace WindowsSandboxExtension;

public sealed class Program
{
public static IHost? Host
{
get; set;
}

[MTAThread]
public static void Main([System.Runtime.InteropServices.WindowsRuntime.ReadOnlyArray] string[] args)
{
// Set up Logging
Environment.SetEnvironmentVariable("DEVHOME_LOGS_ROOT", Path.Join(Helpers.Logging.LogFolderRoot, "WindowsSandbox"));
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings_WindowsSandbox.json")
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();

Log.Information($"Launched with args: {string.Join(' ', args.ToArray())}");

// Force the app to be single instanced.
// Get or register the main instance.
var mainInstance = AppInstance.FindOrRegisterForKey("mainInstance");
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
if (!mainInstance.IsCurrent)
{
Log.Information($"Not main instance, redirecting.");
mainInstance.RedirectActivationToAsync(activationArgs).AsTask().Wait();
Log.CloseAndFlush();
return;
}

// Build the host container before handling activation.
BuildHostContainer();

// Register for activation redirection.
AppInstance.GetCurrent().Activated += AppActivationRedirected;

if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
{
HandleCOMServerActivation();
}
else
{
Log.Warning("Not being launched as a ComServer... exiting.");
}

Log.CloseAndFlush();
}

private static void AppActivationRedirected(object? sender, Microsoft.Windows.AppLifecycle.AppActivationArguments activationArgs)
{
Log.Information($"Redirected with kind: {activationArgs.Kind}");

// Handle COM server.
if (activationArgs.Kind == ExtendedActivationKind.Launch)
{
var launchActivatedEventArgs = activationArgs.Data as ILaunchActivatedEventArgs;
var args = launchActivatedEventArgs?.Arguments.Split();

if (args?.Length > 0 && args[1] == "-RegisterProcessAsComServer")
{
Log.Information($"Activation COM Registration Redirect: {string.Join(' ', args.ToList())}");
HandleCOMServerActivation();
}
}
}

/// <summary>
/// Creates the host container for the Windows Sandbox Extension application. This can be used to register
/// services and other dependencies throughout the application.
/// </summary>
private static void BuildHostContainer()
{
Host = Microsoft.Extensions.Hosting.Host.
CreateDefaultBuilder().
UseContentRoot(AppContext.BaseDirectory).
UseDefaultServiceProvider((context, options) =>
{
options.ValidateOnBuild = true;
}).
ConfigureServices((context, services) =>
{
// Services
services.AddHttpClient();
services.AddSingleton<IComputeSystemProvider, WindowsSandboxProvider>();
services.AddSingleton<WindowsSandboxExtension>();
}).
Build();
}

private static void HandleCOMServerActivation()
{
Debug.Assert(Host != null, "Host is null");
Log.Information($"Activating COM Server");

// Register and run COM server.
// This could be called by either of the COM registrations, we will do them all to avoid deadlock and bind all on the extension's lifetime.
using var extensionServer = new Microsoft.Windows.DevHome.SDK.ExtensionServer();
var windowsSandboxExtension = Host.Services.GetRequiredService(typeof(WindowsSandboxExtension)).As<WindowsSandboxExtension>();

// We are instantiating extension instance once above, and returning it every time the callback in RegisterExtension below is called.
// This makes sure that only one instance of the extension is alive, which is returned every time the host asks for the IExtension object.
// If you want to instantiate a new instance each time the host asks, create the new instance inside the delegate.
extensionServer.RegisterExtension(() => windowsSandboxExtension, true);

// This will make the main thread wait until the event is signalled by the extension class.
// Since we have single instance of the extension object, we exit as soon as it is disposed.
windowsSandboxExtension.ExtensionDisposedEvent.WaitOne();
Log.Information($"Extension is disposed.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Platform>arm64</Platform>
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishReadyToRunComposite Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRunComposite>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Platform>x64</Platform>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishReadyToRunComposite Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRunComposite>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Platform>x86</Platform>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishReadyToRunComposite Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRunComposite>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
"WindowsSandboxExtension": {
"commandName": "Project",
"commandLineArgs": "-RegisterProcessAsComServer"
}
}
}
Loading

0 comments on commit 34b762e

Please sign in to comment.