[flutter_tools] support flutter run -d winuwp (#82373)

Allow flutter run to work end-to-end with a UWP device.

Uses win32/ffi for the actual launch of the application, injected via
the native API class. This is structured to avoid a g3 dependency.

Install and amuid require powershell scripts for now.

Actually connecting to the observatory requires running a command in an
elevated prompt. Instructions are presented to the user if a terminal is
attached.

This is a rebased version of https://github.com/flutter/flutter/pull/79684
by @jonahwilliams, updated to remove `NativeApi` and replace is with calls
to `uwptool`.

Part of https://github.com/flutter/flutter/issues/82085
diff --git a/packages/flutter_tools/bin/getaumidfromname.ps1 b/packages/flutter_tools/bin/getaumidfromname.ps1
new file mode 100644
index 0000000..d5873e3
--- /dev/null
+++ b/packages/flutter_tools/bin/getaumidfromname.ps1
@@ -0,0 +1,13 @@
+# Copyright 2014 The Flutter Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Retrieves the AMUID from a given application name
+[CmdletBinding()]
+param(
+    [Parameter()]
+    [string]$Name
+)
+$foo = get-appxpackage | Where-Object { $_.Name -like $name }
+$aumid = $foo.packagefamilyname + "!" + (Get-AppxPackageManifest $foo).package.applications.application.id
+Write-Output $aumid
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index f5d404d..a5e7c8d 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -55,6 +55,9 @@
   /// Tools related to subsetting or icon font files.
   fontSubset,
   constFinder,
+
+  // Windows UWP app management tool.
+  uwptool,
 }
 
 /// A subset of [Artifact]s that are platform and build mode independent
@@ -111,8 +114,31 @@
   return getNameForTargetPlatform(platform);
 }
 
+bool _isWindows(TargetPlatform platform) {
+  switch (platform) {
+    case TargetPlatform.windows_x64:
+    case TargetPlatform.windows_uwp_x64:
+      return true;
+    case TargetPlatform.android:
+    case TargetPlatform.android_arm:
+    case TargetPlatform.android_arm64:
+    case TargetPlatform.android_x64:
+    case TargetPlatform.android_x86:
+    case TargetPlatform.darwin:
+    case TargetPlatform.fuchsia_arm64:
+    case TargetPlatform.fuchsia_x64:
+    case TargetPlatform.ios:
+    case TargetPlatform.linux_arm64:
+    case TargetPlatform.linux_x64:
+    case TargetPlatform.tester:
+    case TargetPlatform.web_javascript:
+      return false;
+  }
+  return false;
+}
+
 String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMode mode ]) {
-  final String exe = platform == TargetPlatform.windows_x64 ? '.exe' : '';
+  final String exe = _isWindows(platform) ? '.exe' : '';
   switch (artifact) {
     case Artifact.genSnapshot:
       return 'gen_snapshot';
@@ -162,6 +188,8 @@
       return 'font-subset$exe';
     case Artifact.constFinder:
       return 'const_finder.dart.snapshot';
+    case Artifact.uwptool:
+      return 'uwptool$exe';
   }
   assert(false, 'Invalid artifact $artifact.');
   return null;
@@ -543,6 +571,11 @@
                      .childDirectory(_enginePlatformDirectoryName(platform))
                      .childFile(_artifactToFileName(artifact, platform, mode))
                      .path;
+      case Artifact.uwptool:
+        return _cache.getArtifactDirectory('engine')
+                     .childDirectory('windows-uwp-x64-${getNameForBuildMode(mode ?? BuildMode.debug)}')
+                     .childFile(_artifactToFileName(artifact, platform, mode))
+                     .path;
       default:
         assert(false, 'Artifact $artifact not available for platform $platform.');
         return null;
@@ -824,6 +857,8 @@
       case Artifact.frontendServerSnapshotForEngineDartSdk:
         return _fileSystem.path.join(_hostEngineOutPath, 'gen', artifactFileName);
         break;
+      case Artifact.uwptool:
+        return _fileSystem.path.join(_hostEngineOutPath, artifactFileName);
     }
     assert(false, 'Invalid artifact $artifact.');
     return null;
diff --git a/packages/flutter_tools/lib/src/cmake.dart b/packages/flutter_tools/lib/src/cmake.dart
index 7dd735d..9ad626e 100644
--- a/packages/flutter_tools/lib/src/cmake.dart
+++ b/packages/flutter_tools/lib/src/cmake.dart
@@ -4,6 +4,7 @@
 
 // @dart = 2.8
 
+import 'base/file_system.dart';
 import 'project.dart';
 
 /// Extracts the `BINARY_NAME` from a project's CMake file.
@@ -23,6 +24,23 @@
   return null;
 }
 
+/// Extracts the `PACKAGE_GUID` from a project's CMake file.
+///
+/// Returns `null` if it cannot be found.
+String getCmakePackageGuid(File cmakeFile) {
+  if (!cmakeFile.existsSync()) {
+    return null;
+  }
+  final RegExp nameSetPattern = RegExp(r'^\s*set\(PACKAGE_GUID\s*"(.*)"\s*\)\s*$');
+  for (final String line in cmakeFile.readAsLinesSync()) {
+    final RegExpMatch match = nameSetPattern.firstMatch(line);
+    if (match != null) {
+      return match.group(1);
+    }
+  }
+  return null;
+}
+
 String _escapeBackslashes(String s) {
   return s.replaceAll(r'\', r'\\');
 }
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 4c38bfc..8769d1f 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -64,6 +64,7 @@
 import 'runner/local_engine.dart';
 import 'version.dart';
 import 'web/workflow.dart';
+import 'windows/uwptool.dart';
 import 'windows/visual_studio.dart';
 import 'windows/visual_studio_validator.dart';
 import 'windows/windows_workflow.dart';
@@ -206,6 +207,11 @@
           logger: globals.logger,
           platform: globals.platform
         ),
+        uwptool: UwpTool(
+          artifacts: globals.artifacts,
+          logger: globals.logger,
+          processManager: globals.processManager,
+        ),
       ),
       DevtoolsLauncher: () => DevtoolsServerLauncher(
         processManager: globals.processManager,
diff --git a/packages/flutter_tools/lib/src/flutter_application_package.dart b/packages/flutter_tools/lib/src/flutter_application_package.dart
index 65beb69..11b3f92 100644
--- a/packages/flutter_tools/lib/src/flutter_application_package.dart
+++ b/packages/flutter_tools/lib/src/flutter_application_package.dart
@@ -108,9 +108,7 @@
             ? FuchsiaApp.fromFuchsiaProject(FlutterProject.current().fuchsia)
             : FuchsiaApp.fromPrebuiltApp(applicationBinary);
       case TargetPlatform.windows_uwp_x64:
-        return applicationBinary == null
-            ? WindowsApp.fromWindowsProject(FlutterProject.current().windowsUwp)
-            : WindowsApp.fromPrebuiltApp(applicationBinary);
+        return BuildableUwpApp(project: FlutterProject.current().windowsUwp);
     }
     assert(platform != null);
     return null;
diff --git a/packages/flutter_tools/lib/src/flutter_device_manager.dart b/packages/flutter_tools/lib/src/flutter_device_manager.dart
index 08b005b..10c3158 100644
--- a/packages/flutter_tools/lib/src/flutter_device_manager.dart
+++ b/packages/flutter_tools/lib/src/flutter_device_manager.dart
@@ -35,6 +35,7 @@
 import 'tester/flutter_tester.dart';
 import 'version.dart';
 import 'web/web_device.dart';
+import 'windows/uwptool.dart';
 import 'windows/windows_device.dart';
 import 'windows/windows_workflow.dart';
 
@@ -61,6 +62,7 @@
     @required WindowsWorkflow windowsWorkflow,
     @required Terminal terminal,
     @required CustomDevicesConfig customDevicesConfig,
+    @required UwpTool uwptool,
   }) : deviceDiscoverers =  <DeviceDiscovery>[
     AndroidDevices(
       logger: logger,
@@ -118,6 +120,7 @@
       fileSystem: fileSystem,
       windowsWorkflow: windowsWorkflow,
       featureFlags: featureFlags,
+      uwptool: uwptool,
     ),
     WebDevices(
       featureFlags: featureFlags,
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 0dea5ca..d72f5d7 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -16,6 +16,7 @@
 import 'base/logger.dart';
 import 'build_info.dart';
 import 'bundle.dart' as bundle;
+import 'cmake.dart';
 import 'features.dart';
 import 'flutter_manifest.dart';
 import 'flutter_plugins.dart';
@@ -1221,8 +1222,38 @@
   @override
   String get _childDirectory => 'winuwp';
 
+  File get runnerCmakeFile => _editableDirectory.childDirectory('runner_uwp').childFile('CMakeLists.txt');
+
   /// Eventually this will be used to check if the user's unstable project needs to be regenerated.
   int get projectVersion => int.tryParse(_editableDirectory.childFile('project_version').readAsStringSync());
+
+  /// Retrieve the GUID of the UWP package.
+  String get packageGuid => _packageGuid ??= getCmakePackageGuid(runnerCmakeFile);
+  String _packageGuid;
+
+  File get appManifest => _editableDirectory.childDirectory('runner_uwp').childFile('appxmanifest.in');
+
+  String get packageVersion => _packageVersion ??= parseAppVersion(this);
+  String _packageVersion;
+}
+
+@visibleForTesting
+String parseAppVersion(WindowsUwpProject project) {
+  final File appManifestFile = project.appManifest;
+  if (!appManifestFile.existsSync()) {
+    return null;
+  }
+
+  XmlDocument document;
+  try {
+    document = XmlDocument.parse(appManifestFile.readAsStringSync());
+  } on XmlParserException {
+    throwToolExit('Error parsing $appManifestFile. Please ensure that the appx manifest is a valid XML document and try again.');
+  }
+  for (final XmlElement metaData in document.findAllElements('Identity')) {
+    return metaData.getAttribute('Version');
+  }
+  return null;
 }
 
 /// The Linux sub project.
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index fb8f43d..ff8bf1f 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -1466,7 +1466,9 @@
     case TargetPlatform.fuchsia_x64:
     case TargetPlatform.tester:
     case TargetPlatform.windows_uwp_x64:
-      // No artifacts currently supported.
+      if (featureFlags.isWindowsUwpEnabled) {
+        return DevelopmentArtifact.windowsUwp;
+      }
       return null;
   }
   return null;
diff --git a/packages/flutter_tools/lib/src/windows/application_package.dart b/packages/flutter_tools/lib/src/windows/application_package.dart
index f0961b0..de5e212 100644
--- a/packages/flutter_tools/lib/src/windows/application_package.dart
+++ b/packages/flutter_tools/lib/src/windows/application_package.dart
@@ -75,3 +75,14 @@
   @override
   String get name => project.parent.manifest.appName;
 }
+
+class BuildableUwpApp extends ApplicationPackage {
+  BuildableUwpApp({@required this.project}) : super(id: project.packageGuid);
+
+  final WindowsUwpProject project;
+
+  String get projectVersion => project.packageVersion;
+
+  @override
+  String get name => getCmakeExecutableName(project);
+}
diff --git a/packages/flutter_tools/lib/src/windows/uwptool.dart b/packages/flutter_tools/lib/src/windows/uwptool.dart
new file mode 100644
index 0000000..e752236
--- /dev/null
+++ b/packages/flutter_tools/lib/src/windows/uwptool.dart
@@ -0,0 +1,87 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart = 2.8
+
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+import 'package:process/process.dart';
+
+import '../artifacts.dart';
+import '../base/logger.dart';
+import '../base/process.dart';
+
+/// The uwptool command-line tool.
+///
+/// `uwptool` is a host utility command-line tool that supports a variety of
+/// actions related to Universal Windows Platform (UWP) applications, including
+/// installing and uninstalling apps, querying installed apps, and launching
+/// apps.
+class UwpTool {
+  UwpTool({
+    @required Artifacts artifacts,
+    @required Logger logger,
+    @required ProcessManager processManager,
+  }) : _artifacts = artifacts,
+       _logger = logger,
+       _processUtils = ProcessUtils(processManager: processManager, logger: logger);
+
+  final Artifacts _artifacts;
+  final Logger _logger;
+  final ProcessUtils _processUtils;
+
+  String get _binaryPath  => _artifacts.getArtifactPath(Artifact.uwptool);
+
+  Future<List<String>> listApps() async {
+    final List<String> launchCommand = <String>[
+      _binaryPath,
+      'listapps',
+    ];
+    final RunResult result = await _processUtils.run(launchCommand);
+    if (result.exitCode != 0) {
+      _logger.printError('Failed to list installed UWP apps: ${result.stderr}');
+      return <String>[];
+    }
+    final List<String> appIds = <String>[];
+    for (final String line in result.stdout.toString().split('\n')) {
+      final String appId = line.trim();
+      if (appId.isNotEmpty) {
+        appIds.add(appId);
+      }
+    }
+    return appIds;
+  }
+
+  /// Returns the app ID for the specified package ID.
+  ///
+  /// If no installed application on the system matches the specified GUID,
+  /// returns null.
+  Future<String/*?*/> getAppIdFromPackageId(String packageId) async {
+    for (final String appId in await listApps()) {
+      if (appId.startsWith(packageId)) {
+        return appId;
+      }
+    }
+    return null;
+  }
+
+  /// Launches the app with the specified app ID.
+  ///
+  /// On success, returns the process ID of the launched app, otherwise null.
+  Future<int/*?*/> launchApp(String appId, List<String> args) async {
+    final List<String> launchCommand = <String>[
+      _binaryPath,
+      'launch',
+      appId
+    ] + args;
+    final RunResult result = await _processUtils.run(launchCommand);
+    if (result.exitCode != 0) {
+      _logger.printError('Failed to launch app $appId: ${result.stderr}');
+      return null;
+    }
+    // Read the process ID from stdout.
+    return int.tryParse(result.stdout.toString().trim());
+  }
+}
diff --git a/packages/flutter_tools/lib/src/windows/windows_device.dart b/packages/flutter_tools/lib/src/windows/windows_device.dart
index aa4a3b1..b657739 100644
--- a/packages/flutter_tools/lib/src/windows/windows_device.dart
+++ b/packages/flutter_tools/lib/src/windows/windows_device.dart
@@ -4,20 +4,26 @@
 
 // @dart = 2.8
 
+import 'dart:async';
+
 import 'package:meta/meta.dart';
 import 'package:process/process.dart';
 
 import '../application_package.dart';
 import '../base/file_system.dart';
+import '../base/io.dart';
 import '../base/logger.dart';
 import '../base/os.dart';
+import '../base/utils.dart';
 import '../build_info.dart';
 import '../desktop_device.dart';
 import '../device.dart';
+import '../device_port_forwarder.dart';
 import '../features.dart';
 import '../project.dart';
 import 'application_package.dart';
 import 'build_windows.dart';
+import 'uwptool.dart';
 import 'windows_workflow.dart';
 
 /// A device that represents a desktop Windows target.
@@ -71,21 +77,33 @@
 }
 
 // A device that represents a desktop Windows UWP target.
-class WindowsUWPDevice extends DesktopDevice {
+class WindowsUWPDevice extends Device {
   WindowsUWPDevice({
     @required ProcessManager processManager,
     @required Logger logger,
     @required FileSystem fileSystem,
     @required OperatingSystemUtils operatingSystemUtils,
-  }) : super(
-      'winuwp',
-      platformType: PlatformType.windows,
-      ephemeral: false,
-      processManager: processManager,
-      logger: logger,
-      fileSystem: fileSystem,
-      operatingSystemUtils: operatingSystemUtils,
-  );
+    @required UwpTool uwptool,
+  }) : _logger = logger,
+       _processManager = processManager,
+       _operatingSystemUtils = operatingSystemUtils,
+       _fileSystem = fileSystem,
+       _uwptool = uwptool,
+       super(
+         'winuwp',
+         platformType: PlatformType.windows,
+         ephemeral: false,
+         category: Category.desktop,
+       );
+
+  final ProcessManager _processManager;
+  final Logger _logger;
+  final FileSystem _fileSystem;
+  final OperatingSystemUtils _operatingSystemUtils;
+  final UwpTool _uwptool;
+  BuildMode _buildMode;
+
+  int _processId;
 
   @override
   bool isSupported() => true;
@@ -98,29 +116,150 @@
 
   @override
   bool isSupportedForProject(FlutterProject flutterProject) {
-    // TODO(flutter): update with detection once FlutterProject knows
-    // about the UWP structure.
-    return true;
+    return flutterProject.windowsUwp.existsSync();
   }
 
   @override
-  Future<void> buildForDevice(
-    covariant WindowsApp package, {
+  void clearLogs() { }
+
+  @override
+  Future<void> dispose() async { }
+
+  @override
+  Future<String> get emulatorId => null;
+
+  @override
+  FutureOr<DeviceLogReader> getLogReader({covariant BuildableUwpApp app, bool includePastLogs = false}) {
+    return NoOpDeviceLogReader('winuwp');
+  }
+
+  @override
+  Future<bool> installApp(covariant BuildableUwpApp app, {String userIdentifier}) async {
+    /// The cmake build generates an install powershell script.
+    /// build\winuwp\runner_uwp\AppPackages\<app-name>\<app-name>_<app-version>_<cmake-config>\Add-AppDevPackage.ps1
+    final String binaryName = app.name;
+    final String packageVersion = app.projectVersion;
+    if (packageVersion == null) {
+      return false;
+    }
+    final String config = toTitleCase(getNameForBuildMode(_buildMode ?? BuildMode.debug));
+    final String generated = '${binaryName}_${packageVersion}_${config}_Test';
+    final ProcessResult result = await _processManager.run(<String>[
+      'powershell.exe',
+      _fileSystem.path.join('build', 'winuwp', 'runner_uwp', 'AppPackages', binaryName, generated, 'install.ps1'),
+    ]);
+    if (result.exitCode != 0) {
+      _logger.printError(result.stdout.toString());
+      _logger.printError(result.stderr.toString());
+    }
+    return result.exitCode == 0;
+  }
+
+  @override
+  Future<bool> isAppInstalled(covariant ApplicationPackage app, {String userIdentifier}) async => false;
+
+  @override
+  Future<bool> isLatestBuildInstalled(covariant ApplicationPackage app) async => false;
+
+  @override
+  Future<bool> get isLocalEmulator async => false;
+
+  @override
+  DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder();
+
+  @override
+  Future<String> get sdkNameAndVersion async => '';
+
+  @override
+  Future<LaunchResult> startApp(covariant BuildableUwpApp package, {
     String mainPath,
-    BuildInfo buildInfo,
+    String route,
+    DebuggingOptions debuggingOptions,
+    Map<String, dynamic> platformArgs,
+    bool prebuiltApplication = false,
+    bool ipv6 = false,
+    String userIdentifier,
   }) async {
-    await buildWindowsUwp(
-      FlutterProject.current().windowsUwp,
-      buildInfo,
-      target: mainPath,
-    );
+    _buildMode = debuggingOptions.buildInfo.mode;
+    if (!prebuiltApplication) {
+      await buildWindowsUwp(
+        package.project,
+        debuggingOptions.buildInfo,
+        target: mainPath,
+      );
+    }
+    if (!await isAppInstalled(package) && !await installApp(package)) {
+      _logger.printError('Failed to install app package');
+      return LaunchResult.failed();
+    }
+
+    final String guid = package.id;
+    if (guid == null) {
+      _logger.printError('Could not find PACKAGE_GUID in ${package.project.runnerCmakeFile.path}');
+      return LaunchResult.failed();
+    }
+
+    final String appId = await _uwptool.getAppIdFromPackageId(guid);
+
+    if (debuggingOptions.buildInfo.mode.isRelease) {
+      _processId = await _uwptool.launchApp(appId, <String>[]);
+      return _processId != null ? LaunchResult.succeeded() : LaunchResult.failed();
+    }
+
+    /// If the terminal is attached, prompt the user to open the firewall port.
+    if (_logger.terminal.stdinHasTerminal) {
+      await _logger.terminal.promptForCharInput(<String>['Y', 'y'], logger: _logger,
+        prompt: 'To continue start an admin cmd prompt and run the following command:\n'
+        '   checknetisolation loopbackexempt -is -n=$appId\n'
+        'Press "Y/y" once this is complete.'
+      );
+    }
+
+    /// Currently we do not have a way to discover the VM Service URI.
+    final int port = debuggingOptions.deviceVmServicePort ?? await _operatingSystemUtils.findFreePort();
+    final List<String> args = <String>[
+      '--observatory-port=$port',
+      '--disable-service-auth-codes',
+      '--enable-dart-profiling',
+      if (debuggingOptions.startPaused) '--start-paused',
+      if (debuggingOptions.useTestFonts) '--use-test-fonts',
+      if (debuggingOptions.debuggingEnabled) ...<String>[
+        '--enable-checked-mode',
+        '--verify-entry-points',
+      ],
+      if (debuggingOptions.enableSoftwareRendering) '--enable-software-rendering',
+      if (debuggingOptions.skiaDeterministicRendering) '--skia-deterministic-rendering',
+      if (debuggingOptions.traceSkia) '--trace-skia',
+      if (debuggingOptions.traceAllowlist != null) '--trace-allowlist="${debuggingOptions.traceAllowlist}"',
+      if (debuggingOptions.endlessTraceBuffer) '--endless-trace-buffer',
+      if (debuggingOptions.dumpSkpOnShaderCompilation) '--dump-skp-on-shader-compilation',
+      if (debuggingOptions.verboseSystemLogs) '--verbose-logging',
+      if (debuggingOptions.cacheSkSL) '--cache-sksl',
+      if (debuggingOptions.purgePersistentCache) '--purge-persistent-cache',
+      if (platformArgs['trace-startup'] as bool ?? false) '--trace-startup',
+    ];
+    _processId = await _uwptool.launchApp(appId, args);
+    if (_processId == null) {
+      return LaunchResult.failed();
+    }
+    return LaunchResult.succeeded(observatoryUri: Uri.parse('http://localhost:$port'));
   }
 
   @override
-  String executablePathForDevice(ApplicationPackage package, BuildMode buildMode) {
-    // TODO(flutter): update once application package factory knows about UWP bundle.
-    throw UnsupportedError('Windows UWP device not implemented.');
+  Future<bool> stopApp(covariant BuildableUwpApp app, {String userIdentifier}) async {
+    if (_processId != null) {
+      return _processManager.killPid(_processId);
+    }
+    return false;
   }
+
+  @override
+  Future<bool> uninstallApp(covariant BuildableUwpApp app, {String userIdentifier}) async {
+    return false;
+  }
+
+  @override
+  FutureOr<bool> supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
 }
 
 class WindowsDevices extends PollingDeviceDiscovery {
@@ -131,12 +270,14 @@
     @required OperatingSystemUtils operatingSystemUtils,
     @required WindowsWorkflow windowsWorkflow,
     @required FeatureFlags featureFlags,
+    @required UwpTool uwptool,
   }) : _fileSystem = fileSystem,
       _logger = logger,
       _processManager = processManager,
       _operatingSystemUtils = operatingSystemUtils,
       _windowsWorkflow = windowsWorkflow,
       _featureFlags = featureFlags,
+      _uwptool = uwptool,
       super('windows devices');
 
   final FileSystem _fileSystem;
@@ -145,6 +286,7 @@
   final OperatingSystemUtils _operatingSystemUtils;
   final WindowsWorkflow _windowsWorkflow;
   final FeatureFlags _featureFlags;
+  final UwpTool _uwptool;
 
   @override
   bool get supportsPlatform => _windowsWorkflow.appliesToHostPlatform;
@@ -170,6 +312,7 @@
           logger: _logger,
           processManager: _processManager,
           operatingSystemUtils: _operatingSystemUtils,
+          uwptool: _uwptool,
         )
     ];
   }
diff --git a/packages/flutter_tools/test/general.shard/windows/project_test.dart b/packages/flutter_tools/test/general.shard/windows/project_test.dart
new file mode 100644
index 0000000..9b907c4
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/windows/project_test.dart
@@ -0,0 +1,140 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart = 2.8
+
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/project.dart';
+
+import '../../src/common.dart';
+
+const String kExampleManifest = r'''
+<?xml version="1.0" encoding="utf-8"?>
+<Package
+  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
+  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
+  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
+  IgnorableNamespaces="uap mp">
+
+  <Identity Name="@PACKAGE_GUID@" Publisher="CN=CMake Test Cert" Version="2.3.1.4" />
+  <mp:PhoneIdentity PhoneProductId="@PACKAGE_GUID@" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
+
+  <Properties>
+    <DisplayName>@SHORT_NAME@</DisplayName>
+    <PublisherDisplayName>CMake Test Cert</PublisherDisplayName>
+    <Logo>Assets/StoreLogo.png</Logo>
+  </Properties>
+
+  <Dependencies>
+    <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.65535.65535" />
+  </Dependencies>
+
+  <Resources>
+    <Resource Language="x-generate" />
+  </Resources>
+  <Applications>
+    <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="@SHORT_NAME@.App">
+      <uap:VisualElements
+        DisplayName="@SHORT_NAME@"
+        Description="@SHORT_NAME@"
+        BackgroundColor="#336699"
+        Square150x150Logo="Assets/Square150x150Logo.png"
+        Square44x44Logo="Assets/Square44x44Logo.png"
+        >
+        <uap:SplashScreen Image="Assets/SplashScreen.png" />
+      </uap:VisualElements>
+    </Application>
+  </Applications>
+   <Capabilities>
+    <Capability Name="internetClientServer"/>
+    <Capability Name="internetClient"/>
+    <Capability Name="privateNetworkClientServer"/>
+    <Capability Name="codeGeneration"/></Capabilities>
+</Package>
+''';
+
+void main() {
+  testWithoutContext('Project can parse the app version from the appx manifest', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    fileSystem.file('winuwp/runner_uwp/appxmanifest.in')
+      ..createSync(recursive: true)
+      ..writeAsStringSync(kExampleManifest);
+
+    final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+
+    expect(flutterProject.windowsUwp.packageVersion, '2.3.1.4');
+  });
+
+  testWithoutContext('Project returns null if appx manifest does not exist', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+
+    final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+
+    expect(flutterProject.windowsUwp.packageVersion, null);
+  });
+
+  testWithoutContext('Project throws a tool exit if appxmanifest is not valid xml', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    fileSystem.file('winuwp/runner_uwp/appxmanifest.in')
+      ..createSync(recursive: true)
+      ..writeAsStringSync('[');
+
+    final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+
+    expect(() => flutterProject.windowsUwp.packageVersion, throwsToolExit());
+  });
+
+  testWithoutContext('Can parse the PACKAGE_GUID from the Cmake manifest', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    fileSystem.file('winuwp/runner_uwp/CMakeLists.txt')
+      ..createSync(recursive: true)
+      ..writeAsStringSync(r'''
+cmake_minimum_required (VERSION 3.8)
+set(CMAKE_SYSTEM_NAME WindowsStore)
+set(CMAKE_SYSTEM_VERSION 10.0)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED YES)
+
+include(CMakePrintHelpers)
+
+project ("TestBedUWP")
+
+set(APP_MANIFEST_NAME Package.appxmanifest)
+set(APP_MANIFEST_TARGET_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${APP_MANIFEST_NAME})
+set(SHORT_NAME ${BINARY_NAME})
+set(PACKAGE_GUID "F941A77F-8AE1-4E3E-9611-68FBD3C62AE8")
+
+''');
+
+    final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+
+    expect(flutterProject.windowsUwp.packageGuid, 'F941A77F-8AE1-4E3E-9611-68FBD3C62AE8');
+  });
+
+  testWithoutContext('Returns null if the PACKAGE_GUID cannot be found in the Cmake file', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    fileSystem.file('winuwp/runner_uwp/CMakeLists.txt')
+      ..createSync(recursive: true)
+      ..writeAsStringSync(r'''
+cmake_minimum_required (VERSION 3.8)
+set(CMAKE_SYSTEM_NAME WindowsStore)
+set(CMAKE_SYSTEM_VERSION 10.0)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED YES)
+
+include(CMakePrintHelpers)
+
+project ("TestBedUWP")
+
+set(APP_MANIFEST_NAME Package.appxmanifest)
+set(APP_MANIFEST_TARGET_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${APP_MANIFEST_NAME})
+set(SHORT_NAME ${BINARY_NAME})
+''');
+
+    final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+
+    expect(flutterProject.windowsUwp.packageGuid, null);
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart b/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart
index f145b5e..41637a8 100644
--- a/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart
@@ -9,10 +9,12 @@
 import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/device.dart';
 import 'package:flutter_tools/src/features.dart';
 import 'package:flutter_tools/src/project.dart';
 import 'package:flutter_tools/src/windows/application_package.dart';
+import 'package:flutter_tools/src/windows/uwptool.dart';
 import 'package:flutter_tools/src/windows/windows_device.dart';
 import 'package:flutter_tools/src/windows/windows_workflow.dart';
 import 'package:test/fake.dart';
@@ -42,14 +44,14 @@
 
   testWithoutContext('WindowsUwpDevice defaults', () async {
     final WindowsUWPDevice windowsDevice = setUpWindowsUwpDevice();
-    final PrebuiltWindowsApp windowsApp = PrebuiltWindowsApp(executable: 'foo');
+    final FakeBuildableUwpApp package = FakeBuildableUwpApp();
 
     expect(await windowsDevice.targetPlatform, TargetPlatform.windows_uwp_x64);
     expect(windowsDevice.name, 'Windows (UWP)');
-    expect(await windowsDevice.installApp(windowsApp), true);
-    expect(await windowsDevice.uninstallApp(windowsApp), true);
-    expect(await windowsDevice.isLatestBuildInstalled(windowsApp), true);
-    expect(await windowsDevice.isAppInstalled(windowsApp), true);
+    expect(await windowsDevice.installApp(package), true);
+    expect(await windowsDevice.uninstallApp(package), false);
+    expect(await windowsDevice.isLatestBuildInstalled(package), false);
+    expect(await windowsDevice.isAppInstalled(package), false);
     expect(windowsDevice.category, Category.desktop);
 
     expect(windowsDevice.supportsRuntimeMode(BuildMode.debug), true);
@@ -69,6 +71,7 @@
       logger: BufferLogger.test(),
       processManager: FakeProcessManager.any(),
       fileSystem: MemoryFileSystem.test(),
+      uwptool: FakeUwpTool(),
     ).devices, <Device>[]);
   });
 
@@ -83,6 +86,7 @@
       processManager: FakeProcessManager.any(),
       fileSystem: MemoryFileSystem.test(),
       featureFlags: TestFeatureFlags(isWindowsEnabled: true),
+      uwptool: FakeUwpTool(),
     ).devices, hasLength(1));
   });
 
@@ -98,6 +102,7 @@
       processManager: FakeProcessManager.any(),
       fileSystem: MemoryFileSystem.test(),
       featureFlags: featureFlags,
+      uwptool: FakeUwpTool(),
     ).devices, hasLength(2));
   });
 
@@ -112,6 +117,7 @@
       processManager: FakeProcessManager.any(),
       fileSystem: MemoryFileSystem.test(),
       featureFlags: TestFeatureFlags(isWindowsEnabled: true),
+      uwptool: FakeUwpTool(),
     );
     // Timeout ignored.
     final List<Device> devices = await windowsDevices.discoverDevices(timeout: const Duration(seconds: 10));
@@ -159,6 +165,70 @@
     expect(windowsDevice.executablePathForDevice(fakeApp, BuildMode.profile), 'profile/executable');
     expect(windowsDevice.executablePathForDevice(fakeApp, BuildMode.release), 'release/executable');
   });
+
+  testWithoutContext('WinUWPDevice can launch application', () async {
+    Cache.flutterRoot = '';
+    final FakeUwpTool uwptool = FakeUwpTool();
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
+      const FakeCommand(command: <String>[
+        'powershell.exe',
+        'build/winuwp/runner_uwp/AppPackages/testapp/testapp_1.2.3.4_Debug_Test/install.ps1',
+      ]),
+    ]);
+    final WindowsUWPDevice windowsDevice = setUpWindowsUwpDevice(
+      fileSystem: fileSystem,
+      processManager: processManager,
+      uwptool: uwptool,
+    );
+    final FakeBuildableUwpApp package = FakeBuildableUwpApp();
+
+    final LaunchResult result = await windowsDevice.startApp(
+      package,
+      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
+      prebuiltApplication: true,
+      platformArgs: <String, Object>{},
+    );
+
+    expect(result.started, true);
+    expect(uwptool.launchRequests.single.appId, 'PACKAGE-ID_asdfghjkl');
+    expect(uwptool.launchRequests.single.args, <String>[
+      '--observatory-port=12345',
+      '--disable-service-auth-codes',
+      '--enable-dart-profiling',
+      '--enable-checked-mode',
+      '--verify-entry-points',
+    ]);
+  });
+
+   testWithoutContext('WinUWPDevice can launch application in release mode', () async {
+    Cache.flutterRoot = '';
+    final FakeUwpTool uwptool = FakeUwpTool();
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
+      const FakeCommand(command: <String>[
+        'powershell.exe',
+        'build/winuwp/runner_uwp/AppPackages/testapp/testapp_1.2.3.4_Release_Test/install.ps1',
+      ]),
+    ]);
+    final WindowsUWPDevice windowsDevice = setUpWindowsUwpDevice(
+      fileSystem: fileSystem,
+      processManager: processManager,
+      uwptool: uwptool,
+    );
+    final FakeBuildableUwpApp package = FakeBuildableUwpApp();
+
+    final LaunchResult result = await windowsDevice.startApp(
+      package,
+      debuggingOptions: DebuggingOptions.enabled(BuildInfo.release),
+      prebuiltApplication: true,
+      platformArgs: <String, Object>{},
+    );
+
+    expect(result.started, true);
+    expect(uwptool.launchRequests.single.appId, 'PACKAGE-ID_asdfghjkl');
+    expect(uwptool.launchRequests.single.args, <String>[]);
+  });
 }
 
 FlutterProject setUpFlutterProject(Directory directory) {
@@ -186,12 +256,14 @@
   FileSystem fileSystem,
   Logger logger,
   ProcessManager processManager,
+  UwpTool uwptool,
 }) {
   return WindowsUWPDevice(
     fileSystem: fileSystem ?? MemoryFileSystem.test(),
     logger: logger ?? BufferLogger.test(),
     processManager: processManager ?? FakeProcessManager.any(),
     operatingSystemUtils: FakeOperatingSystemUtils(),
+    uwptool: uwptool ?? FakeUwpTool(),
   );
 }
 
@@ -199,3 +271,52 @@
   @override
   String executable(BuildMode buildMode) => '${buildMode.name}/executable';
 }
+
+class FakeBuildableUwpApp extends Fake implements BuildableUwpApp {
+  @override
+  String get id => 'PACKAGE-ID';
+  @override
+  String get name => 'testapp';
+  @override
+  String get projectVersion => '1.2.3.4';
+}
+
+class FakeUwpTool implements UwpTool {
+  final List<_LaunchRequest> launchRequests = <_LaunchRequest>[];
+  final List<_LookupAppIdRequest> lookupAppIdRequests = <_LookupAppIdRequest>[];
+
+  @override
+  Future<List<String>> listApps() async {
+    return <String>[
+      'fb89bf4f-55db-4bcd-8f0b-d8139953e08b',
+      '3e556a66-cb7f-4335-9569-35d5f5e37219',
+      'dfe5d409-a524-4635-b2f8-78a5e9551994',
+      '51e8a06b-02e8-4f76-9131-f20ce114fc34',
+    ];
+  }
+
+  @override
+  Future<String> getAppIdFromPackageId(String packageId) async {
+    lookupAppIdRequests.add(_LookupAppIdRequest(packageId));
+    return '${packageId}_asdfghjkl';
+  }
+
+  @override
+  Future<int> launchApp(String appId, List<String> args) async {
+    launchRequests.add(_LaunchRequest(appId, args));
+    return 42;
+  }
+}
+
+class _LookupAppIdRequest {
+  const _LookupAppIdRequest(this.packageId);
+
+  final String packageId;
+}
+
+class _LaunchRequest {
+  const _LaunchRequest(this.appId, this.args);
+
+  final String appId;
+  final List<String> args;
+}