public IAppInstall InstallApplication(UnrealAppConfig AppConfig)
        {
            // todo - pass this through
            AndroidBuild Build = AppConfig.Build as AndroidBuild;

            // Ensure APK exists
            if (Build == null)
            {
                throw new AutomationException("Invalid build for Android!");
            }

            // kill any currently running instance:
            KillRunningProcess(Build.AndroidPackageName);

            bool SkipDeploy = Globals.Params.ParseParam("SkipDeploy");

            if (SkipDeploy == false)
            {
                // Establish remote directory locations
                string         DeviceStorageQueryCommand = AndroidPlatform.GetStorageQueryCommand();
                IProcessResult StorageQueryResult        = RunAdbDeviceCommand(DeviceStorageQueryCommand);
                string         StorageLocation           = StorageQueryResult.Output.Trim(); // "/mnt/sdcard";

                // remote dir used to save things
                string RemoteDir = StorageLocation + "/UE4Game/" + AppConfig.ProjectName;

                // if not a bulk/dev build, remote dir will be under /{StorageLocation}/Android/data/{PackageName}
                if ((Build.Flags & (BuildFlags.Bulk | BuildFlags.CanReplaceExecutable)) == 0)
                {
                    RemoteDir = StorageLocation + "/Android/data/" + Build.AndroidPackageName + "/files/UE4Game/" + AppConfig.ProjectName;
                }

                string DependencyDir = RemoteDir + "/deps";

                // device artifact path, always clear between runs
                DeviceArtifactPath = string.Format("{0}/{1}/Saved", RemoteDir, AppConfig.ProjectName);
                RunAdbDeviceCommand(string.Format("shell rm -r {0}", DeviceArtifactPath));

                // path for OBB files
                string OBBRemoteDestination = string.Format("{0}/obb/{1}", StorageLocation, Build.AndroidPackageName);

                if (Globals.Params.ParseParam("cleandevice"))
                {
                    Log.Info("Cleaning previous builds due to presence of -cleandevice");

                    // we need to ununstall then install the apk - don't care if it fails, may have been deleted
                    Log.Info("Uninstalling {0}", Build.AndroidPackageName);
                    RunAdbDeviceCommand(string.Format("uninstall {0}", Build.AndroidPackageName));

                    Log.Info("Removing {0}", RemoteDir);
                    RunAdbDeviceCommand(string.Format("shell rm -r {0}", RemoteDir));

                    Log.Info("Removing {0}", OBBRemoteDestination);
                    RunAdbDeviceCommand(string.Format("shell rm -r {0}", OBBRemoteDestination));
                }

                // remote dir on the device, create it if it doesn't exist
                RunAdbDeviceCommand(string.Format("shell mkdir -p {0}/", RemoteDir));

                IProcessResult AdbResult;
                string         AdbCommand;

                // path to the APK to install.
                string ApkPath = Build.SourceApkPath;

                // check for a local newer executable
                if (Globals.Params.ParseParam("dev"))
                {
                    //string ApkFileName = Path.GetFileName(ApkPath);

                    string ApkFileName2 = UnrealHelpers.GetExecutableName(AppConfig.ProjectName, UnrealTargetPlatform.Android, AppConfig.Configuration, AppConfig.ProcessType, "apk");

                    string LocalAPK = Path.Combine(Environment.CurrentDirectory, AppConfig.ProjectName, "Binaries/Android", ApkFileName2);

                    bool LocalFileExists = File.Exists(LocalAPK);
                    bool LocalFileNewer  = LocalFileExists && File.GetLastWriteTime(LocalAPK) > File.GetLastWriteTime(ApkPath);

                    Log.Verbose("Checking for newer binary at {0}", LocalAPK);
                    Log.Verbose("LocalFile exists: {0}. Newer: {1}", LocalFileExists, LocalFileNewer);

                    if (LocalFileExists && LocalFileNewer)
                    {
                        ApkPath = LocalAPK;
                    }
                }

                // first install the APK
                CopyFileToDevice(Build.AndroidPackageName, ApkPath, "");

                // obb files need to be named based on APK version (grrr), so find that out. This should return something like
                // versionCode=2 minSdk=21 targetSdk=21
                string PackageInfo = RunAdbDeviceCommand(string.Format("shell dumpsys package {0} | grep versionCode", Build.AndroidPackageName)).Output;
                var    Match       = Regex.Match(PackageInfo, @"versionCode=([\d\.]+)\s");
                if (Match.Success == false)
                {
                    throw new AutomationException("Failed to find version info for APK!");
                }
                string PackageVersion = Match.Groups[1].ToString();

                // Convert the files from the source to final destination names
                Dictionary <string, string> FilesToInstall = new Dictionary <string, string>();

                Console.WriteLine("trying to copy files over.");
                if (AppConfig.FilesToCopy != null)
                {
                    if (LocalDirectoryMappings.Count == 0)
                    {
                        Console.WriteLine("Populating Directory");
                        PopulateDirectoryMappings(DeviceArtifactPath);
                    }
                    Console.WriteLine("trying to copy files over.");
                    foreach (UnrealFileToCopy FileToCopy in AppConfig.FilesToCopy)
                    {
                        string PathToCopyTo = Path.Combine(LocalDirectoryMappings[FileToCopy.TargetBaseDirectory], FileToCopy.TargetRelativeLocation);
                        if (File.Exists(FileToCopy.SourceFileLocation))
                        {
                            FileInfo SrcInfo = new FileInfo(FileToCopy.SourceFileLocation);
                            SrcInfo.IsReadOnly = false;
                            FilesToInstall.Add(FileToCopy.SourceFileLocation, PathToCopyTo.Replace("\\", "/"));
                            Console.WriteLine("Copying {0} to {1}", FileToCopy.SourceFileLocation, PathToCopyTo);
                        }

                        else
                        {
                            Log.Warning("File to copy {0} not found", FileToCopy);
                        }
                    }
                }

                Build.FilesToInstall.Keys.ToList().ForEach(K =>
                {
                    string SrcPath  = K;
                    string DestPath = Build.FilesToInstall[K];

                    string DestFile = Path.GetFileName(DestPath);

                    // If we installed a new APK we need to change the package version
                    Match OBBMatch = Regex.Match(DestFile, @"main\.(\d+)\.com.*\.obb");
                    if (OBBMatch.Success)
                    {
                        string NewFileName = DestFile.Replace(OBBMatch.Groups[1].ToString(), PackageVersion);
                        DestPath           = DestPath.Replace(DestFile, NewFileName);
                    }

                    DestPath = Regex.Replace(DestPath, "%STORAGE%", StorageLocation, RegexOptions.IgnoreCase);

                    FilesToInstall.Add(SrcPath, DestPath);
                });



                // get a list of files in the destination OBB directory
                AdbResult = RunAdbDeviceCommand(string.Format("shell ls {0}", OBBRemoteDestination));

                // if != 0 then no folder exists
                if (AdbResult.ExitCode == 0)
                {
                    IEnumerable <string> CurrentRemoteFileList = AdbResult.Output.Replace("\r\n", "\n").Split('\n');
                    IEnumerable <string> NewRemoteFileList     = FilesToInstall.Values.Select(F => Path.GetFileName(F));

                    // delete any files that should not be there
                    foreach (string FileName in CurrentRemoteFileList)
                    {
                        if (FileName.StartsWith(".") || FileName.Length == 0)
                        {
                            continue;
                        }

                        if (NewRemoteFileList.Contains(FileName) == false)
                        {
                            RunAdbDeviceCommand(string.Format("shell rm {0}/{1}", OBBRemoteDestination, FileName));
                        }
                    }
                }

                foreach (var KV in FilesToInstall)
                {
                    string LocalFile  = KV.Key;
                    string RemoteFile = KV.Value;

                    CopyFileToDevice(Build.AndroidPackageName, LocalFile, RemoteFile);
                }

                // create a tempfile, insert the command line, and push it over
                string TmpFile = Path.GetTempFileName();

                CommandLineFilePath = string.Format("{0}/UE4CommandLine.txt", RemoteDir);

                // I've seen a weird thing where adb push truncates by a byte, so add some padding...
                File.WriteAllText(TmpFile, AppConfig.CommandLine + "    ");
                AdbCommand = string.Format("push {0} {1}", TmpFile, CommandLineFilePath);
                RunAdbDeviceCommand(AdbCommand);


                File.Delete(TmpFile);
            }
            else
            {
                Log.Info("Skipping install of {0} (-skipdeploy)", Build.AndroidPackageName);
            }

            AndroidAppInstall AppInstall = new AndroidAppInstall(this, AppConfig.ProjectName, Build.AndroidPackageName, AppConfig.CommandLine);

            return(AppInstall);
        }
        /// <summary>
        /// Resign application using local executable and update debug symbols
        /// </summary>
        void ResignApplication(UnrealAppConfig AppConfig)
        {
            // check that we have the signing stuff we need
            string SignProvision    = Globals.Params.ParseValue("signprovision", String.Empty);
            string SignEntitlements = Globals.Params.ParseValue("signentitlements", String.Empty);
            string SigningIdentity  = Globals.Params.ParseValue("signidentity", String.Empty);

            // handle signing provision
            if (string.IsNullOrEmpty(SignProvision) || !File.Exists(SignProvision))
            {
                throw new AutomationException("Absolute path to existing provision must be specified, example: -signprovision=/path/to/myapp.provision");
            }

            // handle entitlements
            // Note this extracts entitlements: which may be useful when using same provision/entitlements?: codesign -d --entitlements :entitlements.plist ~/.gauntletappcache/Payload/Example.app/

            if (string.IsNullOrEmpty(SignEntitlements) || !File.Exists(SignEntitlements))
            {
                throw new AutomationException("Absolute path to existing entitlements must be specified, example: -signprovision=/path/to/entitlements.plist");
            }

            // signing identity
            if (string.IsNullOrEmpty(SigningIdentity))
            {
                throw new AutomationException("Signing identity must be specified, example: -signidentity=\"iPhone Developer: John Smith\"");
            }

            string ProjectName    = AppConfig.ProjectName;
            string BundleName     = Path.GetFileNameWithoutExtension(LocalAppBundle);
            string ExecutableName = UnrealHelpers.GetExecutableName(ProjectName, UnrealTargetPlatform.IOS, AppConfig.Configuration, AppConfig.ProcessType, "");
            string CachedAppPath  = Path.Combine(GauntletAppCache, "Payload", string.Format("{0}.app", BundleName));

            string LocalExecutable = Path.Combine(Environment.CurrentDirectory, ProjectName, string.Format("Binaries/IOS/{0}", ExecutableName));

            if (!File.Exists(LocalExecutable))
            {
                throw new AutomationException("Local executable not found for -dev argument: {0}", LocalExecutable);
            }

            File.WriteAllText(CacheResignedFilename, "The application has been resigned");

            // copy local executable
            FileInfo SrcInfo  = new FileInfo(LocalExecutable);
            string   DestPath = Path.Combine(CachedAppPath, BundleName);

            SrcInfo.CopyTo(DestPath, true);
            Log.Verbose("Copied local executable from {0} to {1}", LocalExecutable, DestPath);

            // copy provision
            SrcInfo  = new FileInfo(SignProvision);
            DestPath = Path.Combine(CachedAppPath, "embedded.mobileprovision");
            SrcInfo.CopyTo(DestPath, true);
            Log.Verbose("Copied provision from {0} to {1}", SignProvision, DestPath);

            // handle symbols
            string LocalSymbolsDir = Path.Combine(Environment.CurrentDirectory, ProjectName, string.Format("Binaries/IOS/{0}.dSYM", ExecutableName));

            DestPath = Path.Combine(GauntletAppCache, string.Format("Symbols/{0}.dSYM", ExecutableName));

            if (Directory.Exists(DestPath))
            {
                Directory.Delete(DestPath, true);
            }

            if (Directory.Exists(LocalSymbolsDir))
            {
                CommandUtils.CopyDirectory_NoExceptions(LocalSymbolsDir, DestPath, true);
            }
            else
            {
                Log.Warning("No symbols found for local build at {0}, removing cached app symbols", LocalSymbolsDir);
            }

            // resign application
            // @todo: this asks for password unless "Always Allow" is selected, also for builders, document how to permanently grant codesign access to keychain
            string SignArgs = string.Format("-f -s \"{0}\" --entitlements \"{1}\" \"{2}\"", SigningIdentity, SignEntitlements, CachedAppPath);

            Log.Info("\nResigning app, please enter keychain password if prompted:\n\ncodesign {0}", SignArgs);
            var Result = IOSBuild.ExecuteCommand("codesign", SignArgs);

            if (Result.ExitCode != 0)
            {
                throw new AutomationException("Failed to resign application");
            }
        }
		public IAppInstall InstallApplication(UnrealAppConfig AppConfig)
		{
			LuminBuild Build = AppConfig.Build as LuminBuild;

			string QualifiedPackageName = GetQualifiedProjectName(AppConfig.ProjectName);

			InstalledAppName = AppConfig.ProjectName;

			// Ensure MPK exists
			if (Build == null)
			{
				throw new AutomationException("Invalid build for Lumin!");
			}

			// kill any currently running instance:
			KillRunningProcess(QualifiedPackageName);

			bool SkipDeploy = Globals.Params.ParseParam("SkipDeploy");

			if (SkipDeploy == false)
			{
				// remote dir used to save things
				string RemoteDir = "/documents/c2/" + AppConfig.ProjectName.ToLower();				
				//string DependencyDir = RemoteDir + "/deps";

				// device artifact path, always clear between runs
				// This clear is from andorid, but currently not needed on mldb.  Everything is removed when you remove the pak
				// If in the future mldb supports a "sidecar" of data like the obb on Android, might need to delete that here
				DeviceArtifactPath = string.Format("{0}/{1}/saved", RemoteDir.ToLower(), AppConfig.ProjectName.ToLower());

				if (Globals.Params.ParseParam("cleandevice"))
				{
					Log.Info("Cleaning previous builds due to presence of -cleandevice");

					// we need to ununstall then install the mpk - don't care if it fails, may have been deleted
					Log.Info("Uninstalling {0}", QualifiedPackageName);
					RunMldbDeviceCommand(string.Format("uninstall {0}", QualifiedPackageName));
				}

				// path to the MPK to install.
				string MpkPath = Build.SourceMpkPath;

				// check for a local newer executable
				if (Globals.Params.ParseParam("dev"))
				{
					string MpkFileName = UnrealHelpers.GetExecutableName(AppConfig.ProjectName, UnrealTargetPlatform.Lumin, AppConfig.Configuration, AppConfig.ProcessType, "mpk");

					string LocalMPK = Path.Combine(Environment.CurrentDirectory, AppConfig.ProjectName, "Binaries/Lumin", MpkFileName);

					bool LocalFileExists = File.Exists(LocalMPK);
					bool LocalFileNewer = LocalFileExists && File.GetLastWriteTime(LocalMPK) > File.GetLastWriteTime(MpkPath);

					Log.Verbose("Checking for newer binary at {0}", LocalMPK);
					Log.Verbose("LocalFile exists: {0}. Newer: {1}", LocalFileExists, LocalFileNewer);

					if (LocalFileExists && LocalFileNewer)
					{
						MpkPath = LocalMPK;
					}
				}

				// first install the MPK
				RunMldbDeviceCommand(string.Format("install -u {0}", MpkPath));

				// If in the future mldb supports a "sidecar" of data like the obb on Android, might need to install that here
				// The Gauntlet.TargetDeviceAndroid.cs implementation should be a good starting point
			}

			LuminAppInstall AppInstall = new LuminAppInstall(this, AppConfig.ProjectName, Build.LuminPackageName, AppConfig.CommandLine);

			return AppInstall;
		}