/// <summary> /// <para>1. list all package</para> /// <para>2. for each package: if operation is 'install' and provisionState is 'success' and no installation lock</para> /// <para> Update operation to null</para> /// </summary> private bool UpdateArmSettingsForSuccessInstallation() { var batchUpdateLock = SiteExtensionBatchUpdateStatusLock.CreateLock(_environment.SiteExtensionSettingsPath); bool isAnyUpdate = false; bool islocked = batchUpdateLock.TryLockOperation(() => { var tracer = _traceFactory.GetTracer(); string[] packageDirs = FileSystemHelpers.GetDirectories(_environment.SiteExtensionSettingsPath); foreach (var dir in packageDirs) { var dirInfo = new DirectoryInfo(dir); // arm setting folder name is same as package id SiteExtensionStatus armSettings = new SiteExtensionStatus(_environment.SiteExtensionSettingsPath, dirInfo.Name, tracer); if (string.Equals(armSettings.Operation, Constants.SiteExtensionOperationInstall, StringComparison.OrdinalIgnoreCase) && string.Equals(armSettings.ProvisioningState, Constants.SiteExtensionProvisioningStateSucceeded, StringComparison.OrdinalIgnoreCase)) { try { armSettings.Operation = null; isAnyUpdate = true; } catch (Exception ex) { tracer.TraceError(ex); // no-op } } } }, TimeSpan.FromSeconds(5)); return(islocked && isAnyUpdate); }
/// <summary> /// <para>Scan every site extensions, check if there is any successful installation</para> /// <para>Looking for below cases:</para> /// <para>if not install to webroot, trigger restart; if install to webroot and with applicationHost.xdt file, trigger restart.</para> /// </summary> /// <param name="siteExtensionStatusRoot">should be $ROOT\site\siteextensions</param> /// <param name="siteExtensionRoot">should be $ROOT\SiteExtensions</param> public static bool IsAnyInstallationRequireRestart(string siteExtensionStatusRoot, string siteExtensionRoot, ITracer tracer, IAnalytics analytics) { try { using (tracer.Step("Checking if there is any installation require site restart ...")) { string[] packageDirs = FileSystemHelpers.GetDirectories(siteExtensionStatusRoot); // folder name is the package id foreach (var dir in packageDirs) { try { DirectoryInfo dirInfo = new DirectoryInfo(dir); var statusSettings = new SiteExtensionStatus(siteExtensionStatusRoot, dirInfo.Name, tracer); if (statusSettings.IsSiteExtensionRequireRestart(siteExtensionRoot)) { return(true); } } catch (Exception ex) { analytics.UnexpectedException(ex, trace: false); tracer.TraceError(ex, "Failed to query {0} under {1}, continus to check others ...", _statusSettingsFileName, dir); } } } } catch (Exception ex) { analytics.UnexpectedException(ex, trace: false); tracer.TraceError(ex, "Not able to query directory under {0}", siteExtensionStatusRoot); } return(false); }
private async Task <TriggersPayload> GetTriggersLocalFiles() { var functionsPath = Environment.CurrentDirectory; if (GlobalCoreToolsSettings.CurrentWorkerRuntime == WorkerRuntime.dotnet) { if (DotnetHelpers.CanDotnetBuild()) { var outputPath = Path.Combine("bin", "output"); await DotnetHelpers.BuildDotnetProject(outputPath, string.Empty, showOutput : false); functionsPath = Path.Combine(Environment.CurrentDirectory, outputPath); } } var functionJsonFiles = FileSystemHelpers .GetDirectories(functionsPath) .Select(d => Path.Combine(d, "function.json")) .Where(FileSystemHelpers.FileExists) .Select(f => (filePath: f, content: FileSystemHelpers.ReadAllTextFromFile(f))); var functionsJsons = functionJsonFiles .Select(t => (filePath: t.filePath, jObject: JsonConvert.DeserializeObject <JObject>(t.content))) .Where(b => b.jObject["bindings"] != null) .ToDictionary(k => Path.GetFileName(Path.GetDirectoryName(k.filePath)), v => v.jObject); var hostJson = JsonConvert.DeserializeObject <JObject>(FileSystemHelpers.ReadAllTextFromFile("host.json")); return(new TriggersPayload { HostJson = hostJson, FunctionsJson = functionsJsons }); }
private static string GetLatestPreInstalledExtensionVersion(string id) { try { IEnumerable <string> pathStrings = FileSystemHelpers.GetDirectories("D:\\Program Files (x86)\\SiteExtensions\\" + id); SemanticVersion maxVersion = pathStrings.Max(path => { string versionString = FileSystemHelpers.DirectoryInfoFromDirectoryName(path).Name; SemanticVersion semVer; if (SemanticVersion.TryParse(versionString, out semVer)) { return(semVer); } else { return(new SemanticVersion(0, 0, 0, 0)); } }); return(maxVersion.ToString()); } catch (IOException) { return(null); } }
private static string GetPreInstalledLatestVersion(string directory) { if (!FileSystemHelpers.DirectoryExists(directory)) { return(null); } string[] pathStrings = FileSystemHelpers.GetDirectories(directory); if (pathStrings.Length == 0) { return(null); } return(pathStrings.Max(path => { string versionString = FileSystemHelpers.DirectoryInfoFromDirectoryName(path).Name; SemanticVersion semVer; if (SemanticVersion.TryParse(versionString, out semVer)) { return semVer; } else { return new SemanticVersion(0, 0, 0, 0); } }).ToString()); }
private void DeleteFilesAndDirsExcept(string fileToKeep, string dirToKeep, ITracer tracer) { // Best effort. Using the "Safe" variants does retries and swallows exceptions but // we may catch something non-obvious. try { var files = FileSystemHelpers.GetFiles(_environment.ZipTempPath, "*") .Where(p => !PathUtilityFactory.Instance.PathsEquals(p, fileToKeep)); foreach (var file in files) { FileSystemHelpers.DeleteFileSafe(file); } var dirs = FileSystemHelpers.GetDirectories(_environment.ZipTempPath) .Where(p => !PathUtilityFactory.Instance.PathsEquals(p, dirToKeep)); foreach (var dir in dirs) { FileSystemHelpers.DeleteDirectorySafe(dir); } } catch (Exception ex) { tracer.TraceError(ex, "Exception encountered during zip folder cleanup"); throw; } }
public async Task <IEnumerable <FunctionEnvelope> > ListFunctionsConfigAsync(FunctionTestData packageLimit = null) // null means no limit { var configList = await Task.WhenAll( FileSystemHelpers .GetDirectories(_environment.FunctionsPath) .Select(d => TryGetFunctionConfigAsync(Path.GetFileName(d), packageLimit))); // TryGetFunctionConfigAsync checks the existence of function.json return(configList.Where(c => c != null)); }
public IEnumerable <string> EnumerateFunctionFiles() { foreach (var functionPath in FileSystemHelpers.GetDirectories(_environment.WebRootPath)) { var functionJson = Path.Combine(functionPath, FunctionJsonFile); if (FileSystemHelpers.FileExists(functionJson)) { yield return(functionJson); } } }
public async Task <IEnumerable <FunctionEnvelope> > ListFunctionsConfigAsync() { var configList = await Task.WhenAll( FileSystemHelpers .GetDirectories(_environment.FunctionsPath) .Select(d => Path.Combine(d, Constants.FunctionsConfigFile)) .Where(FileSystemHelpers.FileExists) .Select(f => TryGetFunctionConfigAsync(Path.GetFileName(Path.GetDirectoryName(f))))); return(configList.Where(c => c != null)); }
private Boolean IsFolderModified(JObject fileObj, string directoryPath) { //Fetch all files in this directory string[] filePaths = FileSystemHelpers.GetFiles(directoryPath, "*"); if (filePaths != null) { foreach (string filePath in filePaths) { Console.WriteLine(filePath); //If manifest does not contain an entry for this file //It means the file was newly added //We need to scan as this is a modification JToken val = null; if (!fileObj.TryGetValue(filePath, out val)) { return(true); } //Modified time in manifest String lastModTime = (string)fileObj[filePath]; //Current modified time String currModTime = FileSystemHelpers.GetDirectoryLastWriteTimeUtc(filePath).ToString(); //If they are different //It means file has been modified after last scan if (!currModTime.Equals(lastModTime)) { return(true); } } } //Fetch all the child directories of this directory string[] direcPaths = FileSystemHelpers.GetDirectories(directoryPath); if (direcPaths != null) { //Do recursive comparison of all files in the child directories foreach (string direcPath in direcPaths) { if (IsFolderModified(fileObj, direcPath)) { return(true); } } } //No modifications found return(false); }
private static IEnumerable <ScanOverviewResult> EnumerateResults(String mainScanDir) { if (FileSystemHelpers.DirectoryExists(mainScanDir)) { foreach (String scanFolderPath in FileSystemHelpers.GetDirectories(mainScanDir)) { ScanOverviewResult result = new ScanOverviewResult(); ScanStatusResult scanStatus = ReadScanStatusFile("", "", Constants.ScanStatusFile, scanFolderPath); result.Status = scanStatus; yield return(result); } } }
public static bool IsAnyPendingLock(string rootPath, ITracer tracer) { bool hasPendingLock = false; try { using (tracer.Step("Checking if there is other pending installation ...")) { string[] packageDirs = FileSystemHelpers.GetDirectories(rootPath); foreach (var dir in packageDirs) { string[] lockFiles = FileSystemHelpers.GetFiles(dir, string.Format("*{0}", LockNameSuffix)); foreach (var file in lockFiles) { // If there's no file then there's no process holding onto it if (!FileSystemHelpers.FileExists(file)) { continue; } try { // If there is a file, lets see if someone has an open handle to it, or if it's // just hanging there for no reason using (FileSystemHelpers.OpenFile(file, FileMode.Open, FileAccess.Write, FileShare.Read)) { } } catch { hasPendingLock = true; break; } } if (hasPendingLock) { break; } } } } catch (Exception ex) { // no-op tracer.TraceError(ex, "Failed to check pending installation lock. Assume there is no pending lock."); } return(hasPendingLock); }
/// <summary> /// <para>1. list all package</para> /// <para>2. for each package: if operation is 'install' and provisionState is 'success' and no installation lock</para> /// <para> Update operation to null</para> /// </summary> private bool UpdateArmSettingsForSuccessInstallation() { var tracer = _traceFactory.GetTracer(); using (tracer.Step("Checking if there is any installation finished recently, if there is one, update its status.")) { var batchUpdateLock = SiteExtensionBatchUpdateStatusLock.CreateLock(_environment.SiteExtensionSettingsPath); bool isAnyUpdate = false; try { batchUpdateLock.LockOperation(() => { string[] packageDirs = FileSystemHelpers.GetDirectories(_environment.SiteExtensionSettingsPath); foreach (var dir in packageDirs) { var dirInfo = new DirectoryInfo(dir); // arm setting folder name is same as package id SiteExtensionStatus armSettings = new SiteExtensionStatus(_environment.SiteExtensionSettingsPath, dirInfo.Name, tracer); if (string.Equals(armSettings.Operation, Constants.SiteExtensionOperationInstall, StringComparison.OrdinalIgnoreCase) && string.Equals(armSettings.ProvisioningState, Constants.SiteExtensionProvisioningStateSucceeded, StringComparison.OrdinalIgnoreCase)) { try { armSettings.Operation = null; isAnyUpdate = true; tracer.Trace("Updated {0}", dir); } catch (Exception ex) { tracer.TraceError(ex); // no-op } } } }, "Updating SiteExtension success status", TimeSpan.FromSeconds(5)); return(isAnyUpdate); } catch (LockOperationException) { return(false); } } }
public static bool IsAnyPendingLock(string rootPath) { bool hasPendingLock = false; try { string[] packageDirs = FileSystemHelpers.GetDirectories(rootPath); foreach (var dir in packageDirs) { string[] lockFiles = FileSystemHelpers.GetFiles(dir, string.Format("*{0}", LockNameSuffix)); foreach (var file in lockFiles) { // If there's no file then there's no process holding onto it if (!FileSystemHelpers.FileExists(file)) { continue; } try { // If there is a file, lets see if someone has an open handle to it, or if it's // just hanging there for no reason using (FileSystemHelpers.OpenFile(file, FileMode.Open, FileAccess.Write, FileShare.Read)) { } } catch { hasPendingLock = true; break; } } if (hasPendingLock) { break; } } } catch { // no-op } return(hasPendingLock); }
private IEnumerable <DeployResult> EnumerateResults() { if (!FileSystemHelpers.DirectoryExists(_environment.DeploymentsPath)) { yield break; } string activeDeploymentId = _status.ActiveDeploymentId; bool isDeploying = IsDeploying; foreach (var id in FileSystemHelpers.GetDirectories(_environment.DeploymentsPath)) { DeployResult result = GetResult(id, activeDeploymentId, isDeploying); if (result != null) { yield return(result); } } }
private static string GetPreInstalledLatestVersion(string directory) { IEnumerable <string> pathStrings = FileSystemHelpers.GetDirectories(directory); SemanticVersion maxVersion = pathStrings.Max(path => { string versionString = FileSystemHelpers.DirectoryInfoFromDirectoryName(path).Name; SemanticVersion semVer; if (SemanticVersion.TryParse(versionString, out semVer)) { return(semVer); } else { return(new SemanticVersion(0, 0, 0, 0)); } }); return(maxVersion.ToString()); }
private void ModifyManifestFile(JObject fileObj, string directoryPath) { //Get all files in this directory string[] filePaths = FileSystemHelpers.GetFiles(directoryPath, "*"); foreach (string filePath in filePaths) { //Get last modified timestamp of this file String timeString = FileSystemHelpers.GetDirectoryLastWriteTimeUtc(filePath).ToString(); //Add it as an entry into the manifest fileObj.Add(filePath, timeString); } //Get all child directories of this directory string[] direcPaths = FileSystemHelpers.GetDirectories(directoryPath); //Do a recursive call to add files of child directories to manifest foreach (string direcPath in direcPaths) { ModifyManifestFile(fileObj, direcPath); } }
private bool IsPreCompiledFunctionApp() { bool isPrecompiled = false; foreach (var directory in FileSystemHelpers.GetDirectories(Environment.CurrentDirectory)) { var functionMetadataFile = Path.Combine(directory, Constants.FunctionJsonFileName); if (File.Exists(functionMetadataFile)) { var functionMetadataFileContent = FileSystemHelpers.ReadAllTextFromFile(functionMetadataFile); var functionMetadata = JsonConvert.DeserializeObject <FunctionMetadata>(functionMetadataFileContent); string extension = Path.GetExtension(functionMetadata?.ScriptFile)?.ToLowerInvariant().TrimStart('.'); isPrecompiled = isPrecompiled || (!string.IsNullOrEmpty(extension) && extension == "dll"); } if (isPrecompiled) { break; } } return(isPrecompiled); }
private static void SetPreInstalledExtensionInfo(SiteExtensionInfo info) { try { string directory = GetPreInstalledDirectory(info.Id); if (info.Type == SiteExtensionInfo.SiteExtensionType.PreInstalledNonKudu) { IEnumerable <string> pathStrings = FileSystemHelpers.GetDirectories(directory); SemanticVersion maxVersion = pathStrings.Max(path => { string versionString = FileSystemHelpers.DirectoryInfoFromDirectoryName(path).Name; SemanticVersion semVer; if (SemanticVersion.TryParse(versionString, out semVer)) { return(semVer); } else { return(new SemanticVersion(0, 0, 0, 0)); } }); info.Version = maxVersion.ToString(); } else if (info.Type == SiteExtensionInfo.SiteExtensionType.PreInstalledKuduModule) { info.Version = typeof(SiteExtensionManager).Assembly.GetName().Version.ToString(); } } catch (IOException) { info.Version = null; } info.LocalIsLatestVersion = true; }
public static void PurgeOldDeploymentsIfNecessary(string deploymentsPath, ITracer tracer, int totalAllowedDeployments) { IEnumerable <string> deploymentNames = FileSystemHelpers.GetDirectories(deploymentsPath); if (deploymentNames.Count() > totalAllowedDeployments) { // Order the files in descending order of the modified date and remove the last (N - allowed zip files). var deploymentsToDelete = deploymentNames.OrderByDescending(fileName => FileSystemHelpers.GetLastWriteTimeUtc(fileName)).Skip(totalAllowedDeployments); foreach (var deploymentName in deploymentsToDelete) { using (tracer.Step("Purging old deployment {0}", deploymentName)) { try { FileSystemHelpers.DeleteDirectorySafe(deploymentName); } catch (Exception ex) { tracer.TraceError(ex, "Unable to delete deployment {0}", deploymentName); } } } } }
internal static void SmartCopy(string sourcePath, string destinationPath, Func <string, bool> existsInPrevious, DirectoryInfoBase sourceDirectory, DirectoryInfoBase destinationDirectory, Func <string, DirectoryInfoBase> createDirectoryInfo) { // Skip source control folder if (FileSystemHelpers.IsSourceControlFolder(sourceDirectory)) { return; } if (!destinationDirectory.Exists) { destinationDirectory.Create(); } // var previousFilesLookup = GetFiles(previousDirectory); var destFilesLookup = FileSystemHelpers.GetFiles(destinationDirectory); var sourceFilesLookup = FileSystemHelpers.GetFiles(sourceDirectory); foreach (var destFile in destFilesLookup.Values) { // If the file doesn't exist in the source, only delete if: // 1. We have no previous directory // 2. We have a previous directory and the file exists there // Trim the start path string previousPath = destFile.FullName.Substring(destinationPath.Length).TrimStart('\\'); if (!sourceFilesLookup.ContainsKey(destFile.Name) && ((existsInPrevious == null) || (existsInPrevious != null && existsInPrevious(previousPath)))) { destFile.Delete(); } } foreach (var sourceFile in sourceFilesLookup.Values) { // Skip the .deployment file if (sourceFile.Name.Equals(DeploymentSettingsProvider.DeployConfigFile, StringComparison.OrdinalIgnoreCase)) { continue; } // if the file exists in the destination then only copy it again if it's // last write time is different than the same file in the source (only if it changed) FileInfoBase targetFile; if (destFilesLookup.TryGetValue(sourceFile.Name, out targetFile) && sourceFile.LastWriteTimeUtc == targetFile.LastWriteTimeUtc) { continue; } // Otherwise, copy the file string path = FileSystemHelpers.GetDestinationPath(sourcePath, destinationPath, sourceFile); OperationManager.Attempt(() => sourceFile.CopyTo(path, overwrite: true)); } var sourceDirectoryLookup = FileSystemHelpers.GetDirectories(sourceDirectory); var destDirectoryLookup = FileSystemHelpers.GetDirectories(destinationDirectory); foreach (var destSubDirectory in destDirectoryLookup.Values) { // If the directory doesn't exist in the source, only delete if: // 1. We have no previous directory // 2. We have a previous directory and the file exists there string previousPath = destSubDirectory.FullName.Substring(destinationPath.Length).TrimStart('\\'); if (!sourceDirectoryLookup.ContainsKey(destSubDirectory.Name) && ((existsInPrevious == null) || (existsInPrevious != null && existsInPrevious(previousPath)))) { destSubDirectory.Delete(recursive: true); } } foreach (var sourceSubDirectory in sourceDirectoryLookup.Values) { DirectoryInfoBase targetSubDirectory; if (!destDirectoryLookup.TryGetValue(sourceSubDirectory.Name, out targetSubDirectory)) { string path = FileSystemHelpers.GetDestinationPath(sourcePath, destinationPath, sourceSubDirectory); targetSubDirectory = createDirectoryInfo(path); } // Sync all sub directories SmartCopy(sourcePath, destinationPath, existsInPrevious, sourceSubDirectory, targetSubDirectory, createDirectoryInfo); } }
internal static async Task CheckNonOptionalSettings(IEnumerable <KeyValuePair <string, string> > secrets, string scriptPath) { try { // FirstOrDefault returns a KeyValuePair<string, string> which is a struct so it can't be null. var azureWebJobsStorage = secrets.FirstOrDefault(pair => pair.Key.Equals("AzureWebJobsStorage", StringComparison.OrdinalIgnoreCase)).Value; var functionJsonFiles = await FileSystemHelpers .GetDirectories(scriptPath) .Select(d => Path.Combine(d, "function.json")) .Where(FileSystemHelpers.FileExists) .Select(async f => (filePath: f, content: await FileSystemHelpers.ReadAllTextFromFileAsync(f))) .WhenAll(); var functionsJsons = functionJsonFiles .Select(t => (filePath: t.filePath, jObject: JsonConvert.DeserializeObject <JObject>(t.content))) .Where(b => b.jObject["bindings"] != null); var allHttpTrigger = functionsJsons .Select(b => b.jObject["bindings"]) .SelectMany(i => i) .Where(b => b?["type"] != null) .Select(b => b["type"].ToString()) .Where(b => b.IndexOf("Trigger") != -1) .All(t => t == "httpTrigger"); if (string.IsNullOrWhiteSpace(azureWebJobsStorage) && !allHttpTrigger) { throw new CliException($"Missing value for AzureWebJobsStorage in {SecretsManager.AppSettingsFileName}. This is required for all triggers other than HTTP. " + $"You can run 'func azure functionapp fetch-app-settings <functionAppName>' or specify a connection string in {SecretsManager.AppSettingsFileName}."); } foreach ((var filePath, var functionJson) in functionsJsons) { foreach (JObject binding in functionJson["bindings"]) { foreach (var token in binding) { if (token.Key == "connection" || token.Key == "apiKey" || token.Key == "accountSid" || token.Key == "authToken") { var appSettingName = token.Value.ToString(); if (string.IsNullOrWhiteSpace(appSettingName)) { ColoredConsole.WriteLine(WarningColor($"Warning: '{token.Key}' property in '{filePath}' is empty.")); } else if (!secrets.Any(v => v.Key.Equals(appSettingName, StringComparison.OrdinalIgnoreCase))) { ColoredConsole .WriteLine(WarningColor($"Warning: Cannot find value named '{appSettingName}' in {SecretsManager.AppSettingsFileName} that matches '{token.Key}' property set on '{binding["type"]?.ToString()}' in '{filePath}'. " + $"You can run 'func azure functionapp fetch-app-settings <functionAppName>' or specify a connection string in {SecretsManager.AppSettingsFileName}.")); } } } } } } catch (Exception e) when(!(e is CliException)) { ColoredConsole.WriteLine(WarningColor($"Warning: unable to verify all settings from {SecretsManager.AppSettingsFileName} and function.json files.")); if (StaticSettings.IsDebug) { ColoredConsole.WriteLine(e); } } }
/// <summary> /// Mark public for testing /// </summary> public IList <string> GetPostBuildActionScripts() { List <string> scriptFilesGroupedAndSorted = new List <string>(); List <string> scriptFolders = new List <string>(); const string extensionEnvVarSuffix = "_EXTENSION_VERSION"; // "/site/deployments/tools/PostDeploymentActions" (can override with %SCM_POST_DEPLOYMENT_ACTIONS_PATH%) // if %SCM_POST_DEPLOYMENT_ACTIONS_PATH% is set, we will support both absolute path or relative parth from "d:/home/site/repository" var customPostDeploymentPath = DeploymentSettings.GetValue(SettingsKeys.PostDeploymentActionsDirectory); var postDeploymentPath = string.IsNullOrEmpty(customPostDeploymentPath) ? Path.Combine(Environment.DeploymentToolsPath, PostDeploymentActions) : Path.Combine(Environment.RepositoryPath, customPostDeploymentPath); if (FileSystemHelpers.DirectoryExists(postDeploymentPath)) { scriptFolders.Add(postDeploymentPath); } // D:\Program Files (x86)\SiteExtensions string siteExtensionFolder = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86), "SiteExtensions"); // Find all enviroment variable with format {package id}_EXTENSION_VERSION, // And find scripts from PostDeploymentActions" folder foreach (DictionaryEntry entry in System.Environment.GetEnvironmentVariables()) { string key = entry.Key.ToString(); string val = entry.Value.ToString(); NuGetVersion ver = null; if (key.EndsWith(extensionEnvVarSuffix, StringComparison.OrdinalIgnoreCase) && (string.Equals("latest", val, StringComparison.OrdinalIgnoreCase) || NuGetVersion.TryParse(val, out ver))) // either latest or a specific version { string extensionName = key.Substring(0, key.Length - extensionEnvVarSuffix.Length); if (ver != null) { // "D:\Program Files (x86)\SiteExtensions", "{ExtensionName}", "{version}", "PostDeploymentActions" // => D:\Program Files (x86)\SiteExtensions\{ExtensionName}\{version}\PostDeploymentActions string scriptFolder = Path.Combine(siteExtensionFolder, extensionName, ver.ToNormalizedString(), PostDeploymentActions); if (FileSystemHelpers.DirectoryExists(scriptFolder)) { scriptFolders.Add(scriptFolder); } } else { // Get the latest version // "D:\Program Files (x86)\SiteExtensions", "{PackageName}" // => D:\Program Files (x86)\SiteExtensions\{PackageName} string packageRoot = Path.Combine(siteExtensionFolder, extensionName); if (FileSystemHelpers.DirectoryExists(packageRoot)) { string[] allVersions = FileSystemHelpers.GetDirectories(packageRoot); // expecting a list of version e.g D:\Program Files (x86)\SiteExtensions\{PackageName}\{version} if (allVersions.Count() > 0) { // latest version comes first Array.Sort(allVersions, (string v1, string v2) => { NuGetVersion ver1 = NuGetVersion.Parse(new DirectoryInfo(v1).Name); NuGetVersion ver2 = NuGetVersion.Parse(new DirectoryInfo(v2).Name); return(ver2.CompareTo(ver1)); }); // "D:\Program Files (x86)\SiteExtensions\{PackageName}\{version}", "PostDeploymentActions" // => D:\Program Files (x86)\SiteExtensions\{PackageName}\{version}\PostDeploymentActions string scriptFolder = Path.Combine(allVersions[0], PostDeploymentActions); if (FileSystemHelpers.DirectoryExists(scriptFolder)) { scriptFolders.Add(scriptFolder); } } } } } } // Find all post action scripts and order file alphabetically for each folder foreach (var sf in scriptFolders) { var files = FileSystemHelpers.GetFiles(sf, "*") .Where(f => f.EndsWith(".cmd", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".sh", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".bat", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)).ToList(); files.Sort(); scriptFilesGroupedAndSorted.AddRange(files); } return(scriptFilesGroupedAndSorted); }