/// <summary>
        /// Uninstalls the target application on the target device
        /// </summary>
        /// <param name="appInfo">Optional cached <see cref="Microsoft.MixedReality.Toolkit.WindowsDevicePortal.ApplicationInfo"/>.</param>
        /// <returns>True, if uninstall was a success.</returns>
        public static async Task <bool> UninstallAppAsync(string packageName, DeviceInfo targetDevice, ApplicationInfo appInfo = null)
        {
            Debug.Assert(!string.IsNullOrEmpty(packageName));

            if (appInfo == null)
            {
                appInfo = await GetApplicationInfoAsync(packageName, targetDevice);
            }

            if (appInfo == null)
            {
                Debug.LogWarning($"Application '{packageName}' not found");
                return(false);
            }

            Debug.Log($"Attempting to uninstall {packageName} on {targetDevice.ToString()}...");

            string query    = $"{string.Format(InstallQuery, FinalizeUrl(targetDevice.IP))}?package={UnityWebRequest.EscapeURL(appInfo.PackageFullName)}";
            var    response = await Rest.DeleteAsync(query, targetDevice.Authorization);

            if (response.Successful)
            {
                Debug.Log($"Successfully uninstalled {packageName} on {targetDevice.ToString()}.");
            }
            else
            if (!response.Successful)
            {
                if (response.ResponseCode == 403 && await RefreshCsrfTokenAsync(targetDevice))
                {
                    return(await UninstallAppAsync(packageName, targetDevice));
                }

                Debug.LogError($"Failed to uninstall {packageName} on {targetDevice.ToString()}");
                Debug.LogError(response.ResponseBody);
                return(false);
            }

            return(true);
        }
        /// <summary>
        /// Installs the target application on the target device.
        /// </summary>
        /// <param name="waitForDone">Should the thread wait until installation is complete?</param>
        /// <returns>True, if Installation was a success.</returns>
        public static async Task <bool> InstallAppAsync(string appFullPath, DeviceInfo targetDevice, bool waitForDone = true)
        {
            Debug.Assert(!string.IsNullOrEmpty(appFullPath));
            var isAuth = await EnsureAuthenticationAsync(targetDevice);

            if (!isAuth)
            {
                return(false);
            }

            Debug.Log($"Starting app install on {targetDevice.ToString()}...");

            // Calculate the cert and dependency paths
            string fileName     = Path.GetFileName(appFullPath);
            string certFullPath = Path.ChangeExtension(appFullPath, ".cer");
            string certName     = Path.GetFileName(certFullPath);

            string arch = "ARM";

            if (appFullPath.Contains("x86"))
            {
                arch = "x86";
            }
            else if (appFullPath.Contains("ARM64"))
            {
                arch = "ARM64";
            }

            string depPath = $@"{Path.GetDirectoryName(appFullPath)}\Dependencies\{arch}\";

            var form = new WWWForm();

            try
            {
                // APPX file
                Debug.Assert(appFullPath != null);
                using (var stream = new FileStream(appFullPath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    using (var reader = new BinaryReader(stream))
                    {
                        form.AddBinaryData(fileName, reader.ReadBytes((int)reader.BaseStream.Length), fileName);
                    }
                }

                // CERT file
                Debug.Assert(certFullPath != null);
                using (var stream = new FileStream(certFullPath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    using (var reader = new BinaryReader(stream))
                    {
                        form.AddBinaryData(certName, reader.ReadBytes((int)reader.BaseStream.Length), certName);
                    }
                }

                // Dependencies
                IOFileInfo[] depFiles = new DirectoryInfo(depPath).GetFiles();
                foreach (IOFileInfo dep in depFiles)
                {
                    using (var stream = new FileStream(dep.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        using (var reader = new BinaryReader(stream))
                        {
                            string depFilename = Path.GetFileName(dep.FullName);
                            form.AddBinaryData(depFilename, reader.ReadBytes((int)reader.BaseStream.Length), depFilename);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                return(false);
            }

            // Query
            string query = $"{string.Format(InstallQuery, FinalizeUrl(targetDevice.IP))}?package={UnityWebRequest.EscapeURL(fileName)}";

            var response = await Rest.PostAsync(query, form, targetDevice.Authorization);

            if (!response.Successful)
            {
                if (response.ResponseCode == 403 && await RefreshCsrfTokenAsync(targetDevice))
                {
                    return(await InstallAppAsync(appFullPath, targetDevice, waitForDone));
                }

                Debug.LogError($"Failed to install {fileName} on {targetDevice.ToString()}.");
                return(false);
            }

            var status = AppInstallStatus.Installing;

            // Wait for done (if requested)
            while (waitForDone && status == AppInstallStatus.Installing)
            {
                status = await GetInstallStatusAsync(targetDevice);

                switch (status)
                {
                case AppInstallStatus.InstallSuccess:
                    Debug.Log($"Successfully installed {fileName} on {targetDevice.ToString()}.");
                    return(true);

                case AppInstallStatus.InstallFail:
                    Debug.LogError($"Failed to install {fileName} on {targetDevice.ToString()}.");
                    return(false);
                }
            }

            return(true);
        }