From 321543025a7dd641743163ca1c5bec0bfb5bd131 Mon Sep 17 00:00:00 2001 From: Jake Friedman Date: Mon, 26 Apr 2021 14:36:21 -0700 Subject: [PATCH 1/8] Check if _ipcHost is NULL before disposing --- src/Service/ServiceExe.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/ServiceExe.cs b/src/Service/ServiceExe.cs index ecc7a106..da8d1e0b 100644 --- a/src/Service/ServiceExe.cs +++ b/src/Service/ServiceExe.cs @@ -1941,7 +1941,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - _ipcHost.Dispose(); + _ipcHost?.Dispose(); _ipcCancellationToken?.Dispose(); _containerHeartbeatToken?.Dispose(); _mutableKey?.Dispose(); From 95a404b42817ef126d4160ae42e78fca9021fea6 Mon Sep 17 00:00:00 2001 From: Jake Friedman Date: Mon, 26 Apr 2021 16:28:20 -0700 Subject: [PATCH 2/8] Fix bug in DisableContainerSupport --- src/Service/ServiceExe.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/ServiceExe.cs b/src/Service/ServiceExe.cs index da8d1e0b..579f768a 100644 --- a/src/Service/ServiceExe.cs +++ b/src/Service/ServiceExe.cs @@ -1804,7 +1804,7 @@ private bool LoadOEMCustomizations() { try { - IsContainerSupportEnabled = Convert.ToBoolean(GetAppSetting(_disableContainerValue) ?? new ArgumentNullException(), CultureInfo.InvariantCulture); + IsContainerSupportEnabled = !Convert.ToBoolean(GetAppSetting(_disableContainerValue) ?? new ArgumentNullException(), CultureInfo.InvariantCulture); } catch (Exception) { From f92ae2b1945c8b743ea60f1de935db2e88a72b1f Mon Sep 17 00:00:00 2001 From: Jake Friedman Date: Mon, 26 Apr 2021 17:09:40 -0700 Subject: [PATCH 3/8] Fix FactoryReset by not tearing down IPC --- ...hestratorClient-ResetService(bool_bool).md | 2 +- ...estratorService-ResetService(bool_bool).md | 2 +- src/App/Resources/en-US/Resources.resw | 8 +++--- src/CoreLibrary/IPCInterface.cs | 14 +++++----- src/Service/CommunicationHandler.cs | 4 +-- src/Service/ServiceExe.cs | 26 ++++++++++++------- 6 files changed, 32 insertions(+), 24 deletions(-) diff --git a/docs/docs/ClientLibrary/Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-ResetService(bool_bool).md b/docs/docs/ClientLibrary/Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-ResetService(bool_bool).md index ea99bfd2..62fac787 100644 --- a/docs/docs/ClientLibrary/Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-ResetService(bool_bool).md +++ b/docs/docs/ClientLibrary/Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-ResetService(bool_bool).md @@ -12,7 +12,7 @@ If true, are logs not deleted. `factoryReset` [System.Boolean](https://docs.microsoft.com/en-us/dotnet/api/System.Boolean 'System.Boolean') -If true, the service is restarted as if it is first boot. +If true, the service is restarted as if it is first boot. NOTE: Network communication is not disabled, connected clients may encounter issues and the 'EnableNetworkAccess' setting will be ignored! #### Returns [System.Threading.Tasks.Task](https://docs.microsoft.com/en-us/dotnet/api/System.Threading.Tasks.Task 'System.Threading.Tasks.Task') diff --git a/docs/docs/CoreLibrary/Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-ResetService(bool_bool).md b/docs/docs/CoreLibrary/Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-ResetService(bool_bool).md index 708576b7..19644996 100644 --- a/docs/docs/CoreLibrary/Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-ResetService(bool_bool).md +++ b/docs/docs/CoreLibrary/Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-ResetService(bool_bool).md @@ -12,5 +12,5 @@ If true, are logs not deleted. `factoryReset` [System.Boolean](https://docs.microsoft.com/en-us/dotnet/api/System.Boolean 'System.Boolean') -If true, the service is restarted as if it is first boot. +If true, the service is restarted as if it is first boot. NOTE: Network communication is not disabled, connected clients may encounter issues and the 'EnableNetworkAccess' setting will be ignored! diff --git a/src/App/Resources/en-US/Resources.resw b/src/App/Resources/en-US/Resources.resw index 494d3032..3bc36b9e 100644 --- a/src/App/Resources/en-US/Resources.resw +++ b/src/App/Resources/en-US/Resources.resw @@ -173,7 +173,7 @@ Could not connect to {0}. Check that the IP address, Server Name and Certificate Hash is correct and that the Factory Orchestrator Service is running on the target IP. - Message displayed to the user if connection to the target machine cannot be established. + Message displayed to the user if connection to the target machine cannot be established. Unable to connect to target IP @@ -283,7 +283,7 @@ Check that the IP address, Server Name and Certificate Hash is correct and that Manually exported FactoryOrchestratorXML files will not be deleted, but will need to be manually imported via "Load FactoryOrchestratorXML file". -If "Factory Reset" is chosen, the service is restarted as if it is first boot. First boot and every boot tasks will re-run. Initial TaskLists will be loaded. "Factory Reset" will temporarily interrupt communication with clients, including this app. +If "Factory Reset" is chosen, the service is restarted as if it is first boot. First boot and every boot tasks will re-run. Initial TaskLists will be loaded. NOTE: Network communication is not disabled, connected clients may encounter issues and the 'EnableNetworkAccess' setting will be ignored! Delete @@ -770,10 +770,10 @@ On retry Certificate Hash: - Hash value of the Certificate of the target machine. + Hash value of the Certificate of the target machine. Server Identity: - Distinguished Name of the target machine. + Distinguished Name of the target machine. \ No newline at end of file diff --git a/src/CoreLibrary/IPCInterface.cs b/src/CoreLibrary/IPCInterface.cs index 7995db40..ba7d403b 100644 --- a/src/CoreLibrary/IPCInterface.cs +++ b/src/CoreLibrary/IPCInterface.cs @@ -6,8 +6,8 @@ using System.Collections; using System.Collections.Generic; using System.Net; -using System.Runtime.InteropServices; - +using System.Runtime.InteropServices; + namespace Microsoft.FactoryOrchestrator.Core { /// @@ -193,7 +193,7 @@ public interface IFactoryOrchestratorService /// Stops all running Tasks and deletes all TaskLists. /// /// If true, are logs not deleted. - /// If true, the service is restarted as if it is first boot. + /// If true, the service is restarted as if it is first boot. NOTE: Network communication is not disabled, connected clients may encounter issues and the 'EnableNetworkAccess' setting will be ignored! void ResetService(bool preserveLogs = false, bool factoryReset = false); /// /// Gets all Service events. @@ -371,14 +371,14 @@ public interface IFactoryOrchestratorService /// /// The Task GUID. /// - TaskBase QueryTask(Guid guid); - + TaskBase QueryTask(Guid guid); + /// /// Gets the AUMIDs of all installed apps on the OS. Requires Windows Device Portal. /// /// The list of app AUMIDs. - List GetInstalledApps(); - + List GetInstalledApps(); + /// /// Gets all installed apps on the OS. Requires Windows Device Portal. /// diff --git a/src/Service/CommunicationHandler.cs b/src/Service/CommunicationHandler.cs index fd5829b2..ac7e5fd4 100644 --- a/src/Service/CommunicationHandler.cs +++ b/src/Service/CommunicationHandler.cs @@ -414,8 +414,8 @@ public void ResetService(bool preserveLogs, bool factoryReset) if (factoryReset) { - // Pause a bit to allow the IPC call to return before we kill it off - Task.Run(() => { System.Threading.Thread.Sleep(500); FOService.Instance.Stop(); FOService.Instance.Start(true, new CancellationToken()); }); + FOService.Instance.Stop(true); + FOService.Instance.Start(true, new CancellationToken()); } } catch (Exception e) diff --git a/src/Service/ServiceExe.cs b/src/Service/ServiceExe.cs index 579f768a..c842f28a 100644 --- a/src/Service/ServiceExe.cs +++ b/src/Service/ServiceExe.cs @@ -447,10 +447,13 @@ public void Start(bool forceUserTaskRerun, CancellationToken cancellationToken) return; } - // Start IPC server on desired port. Only start after all boot tasks are complete. - _ipcHost = FOServiceExe.CreateIpcHost(IsNetworkAccessEnabled, NetworkPort, SSLCertificate); - _ipcCancellationToken = new System.Threading.CancellationTokenSource(); - _ipcHost.RunAsync(_ipcCancellationToken.Token); + // Start IPC server on desired port. Only start if not a reset operation. + if (_ipcHost == null) + { + _ipcHost = FOServiceExe.CreateIpcHost(IsNetworkAccessEnabled, NetworkPort, SSLCertificate); + _ipcCancellationToken = new System.Threading.CancellationTokenSource(); + _ipcHost.RunAsync(_ipcCancellationToken.Token); + } if (IsNetworkAccessEnabled) { @@ -531,10 +534,14 @@ private bool LoadFirstBootStateFile(bool force) /// /// Service stop. /// - public void Stop() - { - // Disable inter process communication interfaces - _ipcCancellationToken?.Cancel(); + public void Stop(bool isFactoryReset = false) + { + if (!isFactoryReset) + { + // Disable inter process communication interfaces if not a reset operation + _ipcCancellationToken?.Cancel(); + } + _containerHeartbeatToken?.Cancel(); _containerClient = null; ContainerGuid = Guid.Empty; @@ -1192,6 +1199,8 @@ public void LogServiceEvent(ServiceEvent serviceEvent) /// public void ExecuteServerBootTasks(CancellationToken cancellationToken) { + IsExecutingBootTasks = true; + if (_isWindows) { // Open Registry Keys @@ -1266,7 +1275,6 @@ public void ExecuteServerBootTasks(CancellationToken cancellationToken) LastEventIndex = 0; LastEventTime = DateTime.MinValue; LocalLoopbackApps = new List(); - IsExecutingBootTasks = true; _openedFiles = new Dictionary(); ContainerGuid = Guid.Empty; From 9f3ef9ec3f4084e56122d8ea58a2b4071409958b Mon Sep 17 00:00:00 2001 From: Jake Friedman Date: Mon, 26 Apr 2021 18:21:18 -0700 Subject: [PATCH 4/8] Support /etc/FactoryOrchestrator/appsettings.json on Linux --- docs/docs/service-configuration.md | 5 +++-- src/Service/ServiceExe.cs | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/docs/service-configuration.md b/docs/docs/service-configuration.md index ea559143..69beb06a 100644 --- a/docs/docs/service-configuration.md +++ b/docs/docs/service-configuration.md @@ -1,10 +1,11 @@ # Factory Orchestrator service configuration using appsettings.json The Factory Orchestrator service has many configurable settings that impact its startup behavior, enabled features, and more. This configuration is easily modified using an [appsettings.json file](https://docs.microsoft.com/en-us/dotnet/core/extensions/configuration-providers#json-configuration-provider). -The appsettings.json file is checked for in two locations: +The appsettings.json file is checked for in the following locations: - The directory where the service executable (Microsoft.FactoryOrchestrator.Service) is located -- The [service log file directory](#factory-orchestrator-service-log-file) (`%ProgramData%\FactoryOrchestrator\` or `/var/log/FactoryOrchestrator/`) +- The [service log file directory](#factory-orchestrator-service-log-file): `%ProgramData%\FactoryOrchestrator\` (Windows) or `/var/log/FactoryOrchestrator/`(Linux) +- (Linux only) The `/etc/FactoryOrchestrator/` directory The following table describes each setting and its usage: diff --git a/src/Service/ServiceExe.cs b/src/Service/ServiceExe.cs index c842f28a..78de7112 100644 --- a/src/Service/ServiceExe.cs +++ b/src/Service/ServiceExe.cs @@ -1694,6 +1694,12 @@ private bool LoadOEMCustomizations() .SetBasePath(AppContext.BaseDirectory) .AddJsonFile(Path.Combine(FOServiceExe.ServiceExeLogFolder, "appsettings.json"), optional: true) .AddJsonFile("appsettings.json", optional: true); + + if (!_isWindows) + { + builder.AddJsonFile("/etc/FactoryOrchestrator/appsettings.json"); + } + Appsettings = builder.Build(); // Look for each setting in the registry (Windows only, OEM Customizations) or in the IConfiguration From ef67fb3a8e687c55b35348504138f2ba138f95f9 Mon Sep 17 00:00:00 2001 From: Jake Friedman Date: Tue, 27 Apr 2021 09:59:26 -0700 Subject: [PATCH 5/8] Support using Device Portal running on any port --- ...toryOrchestratorClient-GetWdpHttpPort().md | 10 +++ ...trator-Client-FactoryOrchestratorClient.md | 1 + ...oryOrchestratorService-GetWdpHttpPort().md | 10 +++ ...trator-Core-IFactoryOrchestratorService.md | 1 + src/App/Resources/en-US/Resources.resw | 2 +- src/App/WdpPage.xaml.cs | 2 +- .../FactoryOrchestratorClient.cs | 2 +- src/CoreLibrary/IPCInterface.cs | 5 ++ .../Microsoft.FactoryOrchestrator.Core.csproj | 3 +- src/CoreLibrary/Resources/Resources.resx | 4 +- src/CoreLibrary/WDPHelpers.cs | 64 +++++++++++++++---- ...icrosoft.FactoryOrchestrator.Server.csproj | 2 +- src/Service/CommunicationHandler.cs | 26 ++++++-- src/Service/ServiceExe.cs | 4 +- 14 files changed, 109 insertions(+), 27 deletions(-) create mode 100644 docs/docs/ClientLibrary/Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-GetWdpHttpPort().md create mode 100644 docs/docs/CoreLibrary/Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-GetWdpHttpPort().md diff --git a/docs/docs/ClientLibrary/Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-GetWdpHttpPort().md b/docs/docs/ClientLibrary/Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-GetWdpHttpPort().md new file mode 100644 index 00000000..df7271be --- /dev/null +++ b/docs/docs/ClientLibrary/Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-GetWdpHttpPort().md @@ -0,0 +1,10 @@ +#### [Microsoft.FactoryOrchestrator.Client](./Microsoft-FactoryOrchestrator-Client.md 'Microsoft.FactoryOrchestrator.Client') +### [Microsoft.FactoryOrchestrator.Client](./Microsoft-FactoryOrchestrator-Client.md 'Microsoft.FactoryOrchestrator.Client').[FactoryOrchestratorClient](./Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient.md 'Microsoft.FactoryOrchestrator.Client.FactoryOrchestratorClient') +## FactoryOrchestratorClient.GetWdpHttpPort() Method +Asynchronously Gets the Windows Device Portal HTTP port. Does not ensure WDP is running or supports HTTP. +```csharp +public System.Threading.Tasks.Task GetWdpHttpPort(); +``` +#### Returns +[System.Threading.Tasks.Task<](https://docs.microsoft.com/en-us/dotnet/api/System.Threading.Tasks.Task-1 'System.Threading.Tasks.Task')[System.Int32](https://docs.microsoft.com/en-us/dotnet/api/System.Int32 'System.Int32')[>](https://docs.microsoft.com/en-us/dotnet/api/System.Threading.Tasks.Task-1 'System.Threading.Tasks.Task') +The HTTP port. diff --git a/docs/docs/ClientLibrary/Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient.md b/docs/docs/ClientLibrary/Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient.md index bdf1d338..52e5b03e 100644 --- a/docs/docs/ClientLibrary/Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient.md +++ b/docs/docs/ClientLibrary/Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient.md @@ -62,6 +62,7 @@ Inheritance [System.Object](https://docs.microsoft.com/en-us/dotnet/api/System.O - [GetServiceVersionString()](./Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-GetServiceVersionString().md 'Microsoft.FactoryOrchestrator.Client.FactoryOrchestratorClient.GetServiceVersionString()') - [GetTaskListGuids()](./Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-GetTaskListGuids().md 'Microsoft.FactoryOrchestrator.Client.FactoryOrchestratorClient.GetTaskListGuids()') - [GetTaskListSummaries()](./Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-GetTaskListSummaries().md 'Microsoft.FactoryOrchestrator.Client.FactoryOrchestratorClient.GetTaskListSummaries()') +- [GetWdpHttpPort()](./Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-GetWdpHttpPort().md 'Microsoft.FactoryOrchestrator.Client.FactoryOrchestratorClient.GetWdpHttpPort()') - [InstallApp(string, System.Collections.Generic.List<string>, string)](./Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-InstallApp(string_System-Collections-Generic-List-string-_string).md 'Microsoft.FactoryOrchestrator.Client.FactoryOrchestratorClient.InstallApp(string, System.Collections.Generic.List<string>, string)') - [IsContainerRunning()](./Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-IsContainerRunning().md 'Microsoft.FactoryOrchestrator.Client.FactoryOrchestratorClient.IsContainerRunning()') - [IsExecutingBootTasks()](./Microsoft-FactoryOrchestrator-Client-FactoryOrchestratorClient-IsExecutingBootTasks().md 'Microsoft.FactoryOrchestrator.Client.FactoryOrchestratorClient.IsExecutingBootTasks()') diff --git a/docs/docs/CoreLibrary/Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-GetWdpHttpPort().md b/docs/docs/CoreLibrary/Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-GetWdpHttpPort().md new file mode 100644 index 00000000..2e0bd66a --- /dev/null +++ b/docs/docs/CoreLibrary/Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-GetWdpHttpPort().md @@ -0,0 +1,10 @@ +#### [Microsoft.FactoryOrchestrator.Core](./Microsoft-FactoryOrchestrator-Core.md 'Microsoft.FactoryOrchestrator.Core') +### [Microsoft.FactoryOrchestrator.Core](./Microsoft-FactoryOrchestrator-Core.md 'Microsoft.FactoryOrchestrator.Core').[IFactoryOrchestratorService](./Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService.md 'Microsoft.FactoryOrchestrator.Core.IFactoryOrchestratorService') +## IFactoryOrchestratorService.GetWdpHttpPort() Method +Gets the Windows Device Portal HTTP port. Does not ensure WDP is running or supports HTTP. +```csharp +int GetWdpHttpPort(); +``` +#### Returns +[System.Int32](https://docs.microsoft.com/en-us/dotnet/api/System.Int32 'System.Int32') +The HTTP port. diff --git a/docs/docs/CoreLibrary/Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService.md b/docs/docs/CoreLibrary/Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService.md index e2251437..7b324223 100644 --- a/docs/docs/CoreLibrary/Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService.md +++ b/docs/docs/CoreLibrary/Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService.md @@ -35,6 +35,7 @@ public interface IFactoryOrchestratorService - [GetServiceVersionString()](./Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-GetServiceVersionString().md 'Microsoft.FactoryOrchestrator.Core.IFactoryOrchestratorService.GetServiceVersionString()') - [GetTaskListGuids()](./Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-GetTaskListGuids().md 'Microsoft.FactoryOrchestrator.Core.IFactoryOrchestratorService.GetTaskListGuids()') - [GetTaskListSummaries()](./Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-GetTaskListSummaries().md 'Microsoft.FactoryOrchestrator.Core.IFactoryOrchestratorService.GetTaskListSummaries()') +- [GetWdpHttpPort()](./Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-GetWdpHttpPort().md 'Microsoft.FactoryOrchestrator.Core.IFactoryOrchestratorService.GetWdpHttpPort()') - [InstallApp(string, System.Collections.Generic.List<string>, string)](./Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-InstallApp(string_System-Collections-Generic-List-string-_string).md 'Microsoft.FactoryOrchestrator.Core.IFactoryOrchestratorService.InstallApp(string, System.Collections.Generic.List<string>, string)') - [IsContainerRunning()](./Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-IsContainerRunning().md 'Microsoft.FactoryOrchestrator.Core.IFactoryOrchestratorService.IsContainerRunning()') - [IsExecutingBootTasks()](./Microsoft-FactoryOrchestrator-Core-IFactoryOrchestratorService-IsExecutingBootTasks().md 'Microsoft.FactoryOrchestrator.Core.IFactoryOrchestratorService.IsExecutingBootTasks()') diff --git a/src/App/Resources/en-US/Resources.resw b/src/App/Resources/en-US/Resources.resw index 3bc36b9e..1930c52f 100644 --- a/src/App/Resources/en-US/Resources.resw +++ b/src/App/Resources/en-US/Resources.resw @@ -720,7 +720,7 @@ On retry Launch Windows Device Portal - Make sure Windows Device Portal is running and try again. + Make sure Windows Device Portal is running and supports HTTP with no authentication, then try again. Failed to launch Windows Device Portal diff --git a/src/App/WdpPage.xaml.cs b/src/App/WdpPage.xaml.cs index e564fae3..5df4fea6 100644 --- a/src/App/WdpPage.xaml.cs +++ b/src/App/WdpPage.xaml.cs @@ -49,7 +49,7 @@ protected override async void OnNavigatedTo(NavigationEventArgs e) if (await IsWindowsDevicePortalRunning()) { string ipAddress = Client.IsLocalHost ? "localhost" : $"{Client.IpAddress.ToString()}"; - string url = "http://" + ipAddress + ":80"; + string url = "http://" + ipAddress + ":" + await Client.GetWdpHttpPort(); Uri myUri = new Uri(url); wdp.Navigate(myUri); } diff --git a/src/ClientLibrary/FactoryOrchestratorClient.cs b/src/ClientLibrary/FactoryOrchestratorClient.cs index de63fce4..2a8f3a31 100644 --- a/src/ClientLibrary/FactoryOrchestratorClient.cs +++ b/src/ClientLibrary/FactoryOrchestratorClient.cs @@ -157,7 +157,7 @@ public async Task SendAndInstallApp(string appFilename, List dependentPa try { - await WDPHelpers.InstallAppWithWDP(appFilename, dependentPackages, certificateFile, IpAddress.ToString()); + await WDPHelpers.InstallAppWithWDP(appFilename, dependentPackages, certificateFile, IpAddress.ToString(), await GetWdpHttpPort()); } catch (Exception ex) { diff --git a/src/CoreLibrary/IPCInterface.cs b/src/CoreLibrary/IPCInterface.cs index ba7d403b..421c423f 100644 --- a/src/CoreLibrary/IPCInterface.cs +++ b/src/CoreLibrary/IPCInterface.cs @@ -287,6 +287,11 @@ public interface IFactoryOrchestratorService /// /// true if the service allows connections over the local network. bool IsNetworkAccessEnabled(); + /// + /// Gets the Windows Device Portal HTTP port. Does not ensure WDP is running or supports HTTP. + /// + /// The HTTP port. + int GetWdpHttpPort(); // TaskList APIs /// diff --git a/src/CoreLibrary/Microsoft.FactoryOrchestrator.Core.csproj b/src/CoreLibrary/Microsoft.FactoryOrchestrator.Core.csproj index cf5d12da..4d755e39 100644 --- a/src/CoreLibrary/Microsoft.FactoryOrchestrator.Core.csproj +++ b/src/CoreLibrary/Microsoft.FactoryOrchestrator.Core.csproj @@ -26,6 +26,7 @@ Designer + @@ -63,7 +64,7 @@ - + diff --git a/src/CoreLibrary/Resources/Resources.resx b/src/CoreLibrary/Resources/Resources.resx index 0dab212c..1ac3a178 100644 --- a/src/CoreLibrary/Resources/Resources.resx +++ b/src/CoreLibrary/Resources/Resources.resx @@ -358,7 +358,7 @@ Error: Failed to launch AUMID: {0} - Error: Device Portal is required for app launch and may not be running on the system. + Error: Device Portal is required for app launch and may not be running on the system or may not support HTTP with no authentication. Error: If it is running, the AUMID may be incorrect. @@ -373,7 +373,7 @@ Windows Device Portal failed with HTTP error - Windows Device Portal must be running to call GetInstalledApps! + Windows Device Portal may not be running or may not support HTTP with no authentication! {0} is only supported on Windows! diff --git a/src/CoreLibrary/WDPHelpers.cs b/src/CoreLibrary/WDPHelpers.cs index 9373b866..9094bea0 100644 --- a/src/CoreLibrary/WDPHelpers.cs +++ b/src/CoreLibrary/WDPHelpers.cs @@ -11,6 +11,7 @@ using System.Runtime.Serialization.Json; using System.Text; using System.Threading.Tasks; +using Microsoft.Win32; namespace Microsoft.FactoryOrchestrator.Core { @@ -79,6 +80,38 @@ public static class WDPHelpers /// public static readonly HttpClient WdpHttpClient = new HttpClient(); + /// + /// Gets the Windows Device Portal HTTP port. + /// + public static int GetWdpHttpPort() + { + using (var osdata = Registry.LocalMachine.OpenSubKey(@"OSDATA\SOFTWARE\Microsoft\Windows\CurrentVersion\WebManagement\Service", false)) + { + if (osdata != null) + { + var osdataPort = osdata.GetValue("HttpPort"); + if (osdataPort != null) + { + return (int)osdataPort; + } + } + } + + using (var sft = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\WebManagement\Service", false)) + { + if (sft != null) + { + var port = sft.GetValue("HttpPort"); + if (port != null) + { + return (int)port; + } + } + } + + return 80; + } + private static HttpMultipartFileContent CreateAppInstallContent(string appFilePath, List dependentAppsFilePaths, string certFilePath) { var content = new HttpMultipartFileContent(); @@ -101,17 +134,19 @@ private static HttpMultipartFileContent CreateAppInstallContent(string appFilePa /// Builds the application installation Uri and generates a unique boundary string for the multipart form data. /// /// The name of the application package. - /// The ip address of the device to install the app on + /// The ip address of the device to install the app on + /// The port for WDP on the target device. /// The endpoint for the install request. /// Unique string used to separate the parts of the multipart form data. private static void CreateAppInstallEndpointAndBoundaryString( string packageName, string ipAddress, + int port, out Uri uri, out string boundaryString) { uri = BuildEndpoint( - new Uri($"http://{ipAddress}"), + new Uri($"http://{ipAddress}:{port}"), "api/app/packagemanager/package", $"package={packageName}"); @@ -135,12 +170,12 @@ private static Uri BuildEndpoint( return new Uri(baseUri, relativePart); } - private static async Task GetInstallStatusAsync(string ipAddress = "localhost") + private static async Task GetInstallStatusAsync(string ipAddress = "localhost", int port = 80) { ApplicationInstallStatus status = ApplicationInstallStatus.None; Uri uri = BuildEndpoint( - new Uri($"http://{ipAddress}"), + new Uri($"http://{ipAddress}:{port}"), "api/app/packagemanager/state"); using (HttpResponseMessage response = await WdpHttpClient.GetAsync(uri).ConfigureAwait(false)) @@ -213,10 +248,11 @@ private static async Task GetInstallStatusAsync(string /// The app package file path. /// The dependent app packages file paths. /// The certificate file path. - /// The ip address of the device to install the app on. + /// The ip address of the device to install the app on. + /// The port for WDP on the target device. /// /// - public static async Task InstallAppWithWDP(string appFilePath, List dependentAppsFilePaths, string certFilePath, string ipAddress = "localhost") + public static async Task InstallAppWithWDP(string appFilePath, List dependentAppsFilePaths, string certFilePath, string ipAddress = "localhost", int port = 80) { ApplicationInstallStatus status = ApplicationInstallStatus.InProgress; @@ -244,7 +280,7 @@ public static async Task InstallAppWithWDP(string appFilePath, List depe } } - CreateAppInstallEndpointAndBoundaryString(Path.GetFileName(appFilePath), ipAddress, out var uri, out _); + CreateAppInstallEndpointAndBoundaryString(Path.GetFileName(appFilePath), ipAddress, port, out var uri, out _); using (var content = CreateAppInstallContent(appFilePath, dependentAppsFilePaths, certFilePath)) { await WdpHttpClient.PostAsync(uri, content); @@ -253,7 +289,7 @@ public static async Task InstallAppWithWDP(string appFilePath, List depe while (status == ApplicationInstallStatus.InProgress) { await Task.Delay(500); - status = await GetInstallStatusAsync(ipAddress); + status = await GetInstallStatusAsync(ipAddress, port); } } @@ -262,17 +298,18 @@ public static async Task InstallAppWithWDP(string appFilePath, List depe /// Closes a running app package application with Windows Device Portal. /// /// The app package to exit . - /// The ip address of the device to exit the app on. + /// The ip address of the device to exit the app on. + /// The port for WDP on the target device. /// /// - public static async Task CloseAppWithWDP(string app, string ipAddress = "localhost") + public static async Task CloseAppWithWDP(string app, string ipAddress = "localhost", int port = 80) { if (string.IsNullOrWhiteSpace(app)) { throw new ArgumentException(Resources.WDPError, nameof(app)); } - var uri = BuildEndpoint(new Uri($"http://{ipAddress}"), + var uri = BuildEndpoint(new Uri($"http://{ipAddress}:{port}"), "api/taskmanager/app", $"package={Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(app))}"); @@ -283,11 +320,12 @@ public static async Task CloseAppWithWDP(string app, string ipAddress = "localho /// Gets the collection of applications installed on the device. /// /// The ip address of the device to query. + /// The port for WDP on the target device. /// AppPackages object containing the list of installed application packages. - public static async Task GetInstalledAppPackagesAsync(string ipAddress = "localhost") + public static async Task GetInstalledAppPackagesAsync(string ipAddress = "localhost", int port = 80) { Uri uri = BuildEndpoint( - new Uri($"http://{ipAddress}"), + new Uri($"http://{ipAddress}:{port}"), "api/app/packagemanager/packages"); var resp = await WdpHttpClient.GetAsync(uri).ConfigureAwait(false); diff --git a/src/ServerLibrary/Microsoft.FactoryOrchestrator.Server.csproj b/src/ServerLibrary/Microsoft.FactoryOrchestrator.Server.csproj index 8d4a1cf8..cca4ea44 100644 --- a/src/ServerLibrary/Microsoft.FactoryOrchestrator.Server.csproj +++ b/src/ServerLibrary/Microsoft.FactoryOrchestrator.Server.csproj @@ -11,7 +11,7 @@ $(OutputRootPath)$(Configuration)/$(Platform)/$(TargetName) - + diff --git a/src/Service/CommunicationHandler.cs b/src/Service/CommunicationHandler.cs index ac7e5fd4..5e1da439 100644 --- a/src/Service/CommunicationHandler.cs +++ b/src/Service/CommunicationHandler.cs @@ -802,12 +802,12 @@ public void TerminateApp(string aumid) throw new FactoryOrchestratorException(string.Format(CultureInfo.CurrentCulture, Resources.WindowsOnlyError, "TerminateApp")); } - var apps = WDPHelpers.GetInstalledAppPackagesAsync().Result; + var apps = WDPHelpers.GetInstalledAppPackagesAsync("localhost", WDPHelpers.GetWdpHttpPort()).Result; var app = apps.Packages.Where(x => x.AppId.Equals(aumid, StringComparison.OrdinalIgnoreCase)).DefaultIfEmpty(null).FirstOrDefault(); if (app != null) { - WDPHelpers.CloseAppWithWDP(app.FullName).Wait(); + WDPHelpers.CloseAppWithWDP(app.FullName, "localhost", WDPHelpers.GetWdpHttpPort()).Wait(); } FOService.Instance.ServiceLogger.LogDebug($"{Resources.Finish}: TerminateApp {aumid}"); @@ -1014,7 +1014,7 @@ public void InstallApp(string appPackagePath, List dependentPackages = n } } - WDPHelpers.InstallAppWithWDP(appPackagePath, dependentPackages, certificateFile).Wait(); + WDPHelpers.InstallAppWithWDP(appPackagePath, dependentPackages, certificateFile, "localhost", WDPHelpers.GetWdpHttpPort()).Wait(); FOService.Instance.ServiceLogger.LogDebug($"{Resources.Finish}: InstallApp {appPackagePath}"); } @@ -1037,7 +1037,7 @@ public List GetInstalledApps() } // Get installed packages on the system - var apps = WDPHelpers.GetInstalledAppPackagesAsync().Result; + var apps = WDPHelpers.GetInstalledAppPackagesAsync("localhost", WDPHelpers.GetWdpHttpPort()).Result; List aumids = apps.Packages.Select(x => x.AppId).ToList(); @@ -1063,7 +1063,7 @@ public List GetInstalledAppsDetailed() } // Get installed packages on the system - var apps = WDPHelpers.GetInstalledAppPackagesAsync().Result; + var apps = WDPHelpers.GetInstalledAppPackagesAsync("localhost", WDPHelpers.GetWdpHttpPort()).Result; FOService.Instance.ServiceLogger.LogDebug($"{Resources.Finish}: GetInstalledAppsDetailed"); return apps.Packages; @@ -1238,5 +1238,21 @@ public bool IsNetworkAccessEnabled() throw; } } + + public int GetWdpHttpPort() + { + try + { + FOService.Instance.ServiceLogger.LogDebug($"{Resources.Start}: GetWdpHttpPort"); + int ret = WDPHelpers.GetWdpHttpPort(); + FOService.Instance.ServiceLogger.LogDebug($"{Resources.Finish}: GetWdpHttpPort"); + return ret; + } + catch (Exception e) + { + FOService.Instance.LogServiceEvent(new ServiceEvent(ServiceEventType.ServiceError, null, e.AllExceptionsToString())); + throw; + } + } } } diff --git a/src/Service/ServiceExe.cs b/src/Service/ServiceExe.cs index 78de7112..a2830c34 100644 --- a/src/Service/ServiceExe.cs +++ b/src/Service/ServiceExe.cs @@ -726,13 +726,13 @@ private void RunTaskRunInContainer(ServerTaskRun hostRun) { // Exit URDC if we launched it. // There may be a preview app & official app installed, close them all - var rdApps = (await WDPHelpers.GetInstalledAppPackagesAsync()).Packages.Where(x => (x.FullName.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase)) && (x.FullName.Contains("RemoteDesktop", StringComparison.OrdinalIgnoreCase))); + var rdApps = (await WDPHelpers.GetInstalledAppPackagesAsync("localhost", WDPHelpers.GetWdpHttpPort())).Packages.Where(x => (x.FullName.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase)) && (x.FullName.Contains("RemoteDesktop", StringComparison.OrdinalIgnoreCase))); foreach (var app in rdApps) { try { - await WDPHelpers.CloseAppWithWDP(app.FullName); + await WDPHelpers.CloseAppWithWDP(app.FullName, "localhost", WDPHelpers.GetWdpHttpPort()); } catch (Exception) { From ce296d5263cbcbbb747da341988dc19d6b97de72 Mon Sep 17 00:00:00 2001 From: Jake Friedman Date: Tue, 27 Apr 2021 10:17:16 -0700 Subject: [PATCH 6/8] Mark /etc/FactoryOrchestrator/appsettings.json optional --- src/Service/ServiceExe.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/ServiceExe.cs b/src/Service/ServiceExe.cs index a2830c34..4b7a216a 100644 --- a/src/Service/ServiceExe.cs +++ b/src/Service/ServiceExe.cs @@ -1697,7 +1697,7 @@ private bool LoadOEMCustomizations() if (!_isWindows) { - builder.AddJsonFile("/etc/FactoryOrchestrator/appsettings.json"); + builder.AddJsonFile("/etc/FactoryOrchestrator/appsettings.json", optional:true); } Appsettings = builder.Build(); From 07d1a29c5b73bd260b0ca86e8fc43bd6b3777ee9 Mon Sep 17 00:00:00 2001 From: Jake Friedman Date: Tue, 27 Apr 2021 10:32:30 -0700 Subject: [PATCH 7/8] Bump version to 10.1.0 --- src/common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common.props b/src/common.props index c3fe36f9..5700b694 100644 --- a/src/common.props +++ b/src/common.props @@ -3,7 +3,7 @@ - 10.0.0 + 10.1.0 From 51c113eb72e39787b8539c4f80645262a0c565dd Mon Sep 17 00:00:00 2001 From: Jake Friedman Date: Tue, 27 Apr 2021 11:17:02 -0700 Subject: [PATCH 8/8] Workaround dotnet issue 50020: Windows container service bug --- .../FactoryOrchestratorServiceTemplate.wm.xml | 2 +- install/InstallFactoryOrchestratorService.ps1 | 2 +- src/Service/ServiceExe.cs | 48 ++++++++++++++----- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/build/internal/FactoryOrchestratorServiceTemplate.wm.xml b/build/internal/FactoryOrchestratorServiceTemplate.wm.xml index fd086594..02a9ec2c 100644 --- a/build/internal/FactoryOrchestratorServiceTemplate.wm.xml +++ b/build/internal/FactoryOrchestratorServiceTemplate.wm.xml @@ -8,7 +8,7 @@ description="Factory Orchestrator Service" displayName="Factory Orchestrator Service" errorControl="normal" - imagePath="%systemroot%\system32\manufacturing\FactoryOrchestrator\Microsoft.FactoryOrchestrator.Service.exe action:run" + imagePath="%systemroot%\system32\manufacturing\FactoryOrchestrator\Microsoft.FactoryOrchestrator.Service.exe -IsService" name="Microsoft.FactoryOrchestrator.Service" objectName="LocalSystem" start="auto" diff --git a/install/InstallFactoryOrchestratorService.ps1 b/install/InstallFactoryOrchestratorService.ps1 index 7ab27ffa..63d95230 100644 --- a/install/InstallFactoryOrchestratorService.ps1 +++ b/install/InstallFactoryOrchestratorService.ps1 @@ -56,7 +56,7 @@ else } else { - $null = New-Service -Name "Microsoft.FactoryOrchestrator" -BinaryPathName "$installdir\Microsoft.FactoryOrchestrator.Service.exe" -Description "Factory Orchestrator service version $Version$" -StartupType Manual + $null = New-Service -Name "Microsoft.FactoryOrchestrator" -BinaryPathName "$installdir\Microsoft.FactoryOrchestrator.Service.exe -IsService" -Description "Factory Orchestrator service version $Version$" -StartupType Manual } Write-Host "Factory Orchestrator service version $Version$ is installed to `"$installdir`" and configured as a Windows service!`n" diff --git a/src/Service/ServiceExe.cs b/src/Service/ServiceExe.cs index 4b7a216a..ea12a69e 100644 --- a/src/Service/ServiceExe.cs +++ b/src/Service/ServiceExe.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -16,7 +16,9 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.WindowsServices; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.EventLog; using Microsoft.FactoryOrchestrator.Client; using Microsoft.FactoryOrchestrator.Core; using Microsoft.FactoryOrchestrator.Server; @@ -106,19 +108,43 @@ public static IHost CreateIpcHost(bool allowNetworkAccess, int port, X509Certifi }).Build(); public static void Main(string[] args) - { - Host.CreateDefaultBuilder(null).UseSystemd().UseWindowsService().ConfigureServices((hostContext, services) => - { - services.AddHostedService(); - }).ConfigureLogging(builder => - { + { #if DEBUG - var _logLevel = LogLevel.Debug; + var _logLevel = LogLevel.Debug; #else - var _logLevel = LogLevel.Information; + var _logLevel = LogLevel.Information; #endif - builder.SetMinimumLevel(_logLevel).AddConsole().AddProvider(new LogFileProvider()); - }).Build().Run(); + var hostBuilder = Host.CreateDefaultBuilder(null).UseSystemd().ConfigureServices((hostContext, services) => + { + services.AddHostedService(); + }).ConfigureLogging(builder => + { + builder.SetMinimumLevel(_logLevel).AddConsole().AddProvider(new LogFileProvider()); + }); + + bool isService = ((args != null) && (args.Length > 0)); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && isService) + { + hostBuilder.UseContentRoot(AppContext.BaseDirectory); + hostBuilder.ConfigureLogging((hostingContext, logging) => + { + logging.AddEventLog(); + logging.SetMinimumLevel(_logLevel); + }) + .ConfigureServices((hostContext, services) => + { + services.AddSingleton(); + services.Configure(settings => + { + if (string.IsNullOrEmpty(settings.SourceName)) + { + settings.SourceName = hostContext.HostingEnvironment.ApplicationName; + } + }); + }); + } + + hostBuilder.Build().Run(); } }