Skip to content

Commit 53af2d9

Browse files
authored
Make delete more robust (fix #1662) (#1667)
1 parent 41dabab commit 53af2d9

File tree

3 files changed

+50
-32
lines changed

3 files changed

+50
-32
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ srcOld/code/bin
44
srcOld/code/obj
55
out
66
test/**/obj
7-
test/**/bin
7+
test/**/bin

src/code/InstallHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ private void MoveFilesIntoInstallPath(
467467
// Delete the directory path before replacing it with the new module.
468468
// If deletion fails (usually due to binary file in use), then attempt restore so that the currently
469469
// installed module is not corrupted.
470-
_cmdletPassedIn.WriteVerbose($"Attempting to delete with restore on failure.'{finalModuleVersionDir}'");
470+
_cmdletPassedIn.WriteVerbose($"Attempting to delete with restore on failure. '{finalModuleVersionDir}'");
471471
Utils.DeleteDirectoryWithRestore(finalModuleVersionDir);
472472
}
473473

src/code/Utils.cs

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
using System.Security;
2121
using Azure.Core;
2222
using Azure.Identity;
23+
using System.Threading.Tasks;
24+
using System.Threading;
2325

2426
namespace Microsoft.PowerShell.PSResourceGet.UtilClasses
2527
{
@@ -42,7 +44,7 @@ public enum MetadataFileType
4244
#region String fields
4345

4446
public static readonly string[] EmptyStrArray = Array.Empty<string>();
45-
public static readonly char[] WhitespaceSeparator = new char[]{' '};
47+
public static readonly char[] WhitespaceSeparator = new char[] { ' ' };
4648
public const string PSDataFileExt = ".psd1";
4749
public const string PSScriptFileExt = ".ps1";
4850
private const string ConvertJsonToHashtableScript = @"
@@ -140,7 +142,7 @@ public static string[] GetStringArray(ArrayList list)
140142
if (list == null) { return null; }
141143

142144
var strArray = new string[list.Count];
143-
for (int i=0; i < list.Count; i++)
145+
for (int i = 0; i < list.Count; i++)
144146
{
145147
strArray[i] = list[i] as string;
146148
}
@@ -161,7 +163,7 @@ public static string[] ProcessNameWildcards(
161163
{
162164
isContainWildcard = true;
163165
errorMsgs = errorMsgsList.ToArray();
164-
return new string[] {"*"};
166+
return new string[] { "*" };
165167
}
166168

167169
isContainWildcard = false;
@@ -179,8 +181,8 @@ public static string[] ProcessNameWildcards(
179181
if (String.Equals(name, "*", StringComparison.InvariantCultureIgnoreCase))
180182
{
181183
isContainWildcard = true;
182-
errorMsgs = new string[] {};
183-
return new string[] {"*"};
184+
errorMsgs = new string[] { };
185+
return new string[] { "*" };
184186
}
185187

186188
if (name.Contains("?") || name.Contains("["))
@@ -275,7 +277,8 @@ public static bool TryGetVersionType(
275277
// eg: 2.8.8.* should translate to the version range "[2.1.3.0,2.1.3.99999]"
276278
modifiedVersion = $"[{versionSplit[0]}.{versionSplit[1]}.{versionSplit[2]}.0,{versionSplit[0]}.{versionSplit[1]}.{versionSplit[2]}.999999]";
277279
}
278-
else {
280+
else
281+
{
279282
error = "Argument for -Version parameter is not in the proper format";
280283
return false;
281284
}
@@ -469,15 +472,15 @@ public static bool TryCreateValidPSCredentialInfo(
469472

470473
try
471474
{
472-
if (!string.IsNullOrEmpty((string) credentialInfoCandidate.Properties[PSCredentialInfo.VaultNameAttribute]?.Value)
473-
&& !string.IsNullOrEmpty((string) credentialInfoCandidate.Properties[PSCredentialInfo.SecretNameAttribute]?.Value))
475+
if (!string.IsNullOrEmpty((string)credentialInfoCandidate.Properties[PSCredentialInfo.VaultNameAttribute]?.Value)
476+
&& !string.IsNullOrEmpty((string)credentialInfoCandidate.Properties[PSCredentialInfo.SecretNameAttribute]?.Value))
474477
{
475478
PSCredential credential = null;
476479
if (credentialInfoCandidate.Properties[PSCredentialInfo.CredentialAttribute] != null)
477480
{
478481
try
479482
{
480-
credential = (PSCredential) credentialInfoCandidate.Properties[PSCredentialInfo.CredentialAttribute].Value;
483+
credential = (PSCredential)credentialInfoCandidate.Properties[PSCredentialInfo.CredentialAttribute].Value;
481484
}
482485
catch (Exception e)
483486
{
@@ -492,8 +495,8 @@ public static bool TryCreateValidPSCredentialInfo(
492495
}
493496

494497
repoCredentialInfo = new PSCredentialInfo(
495-
(string) credentialInfoCandidate.Properties[PSCredentialInfo.VaultNameAttribute].Value,
496-
(string) credentialInfoCandidate.Properties[PSCredentialInfo.SecretNameAttribute].Value,
498+
(string)credentialInfoCandidate.Properties[PSCredentialInfo.VaultNameAttribute].Value,
499+
(string)credentialInfoCandidate.Properties[PSCredentialInfo.SecretNameAttribute].Value,
497500
credential
498501
);
499502

@@ -711,7 +714,7 @@ public static string GetContainerRegistryAccessTokenFromSecretManagement(
711714
string password = new NetworkCredential(string.Empty, secretSecureString).Password;
712715
return password;
713716
}
714-
else if(secretValue is PSCredential psCredSecret)
717+
else if (secretValue is PSCredential psCredSecret)
715718
{
716719
string password = new NetworkCredential(string.Empty, psCredSecret.Password).Password;
717720
return password;
@@ -1120,7 +1123,8 @@ private static void GetStandardPlatformPaths(
11201123
// paths are the same for both Linux and macOS
11211124
localUserDir = Path.Combine(GetHomeOrCreateTempHome(), ".local", "share", "powershell");
11221125
// Create the default data directory if it doesn't exist.
1123-
if (!Directory.Exists(localUserDir)) {
1126+
if (!Directory.Exists(localUserDir))
1127+
{
11241128
Directory.CreateDirectory(localUserDir);
11251129
}
11261130

@@ -1247,7 +1251,7 @@ private static bool TryReadPSDataFile(
12471251
result = psObject.BaseObject;
12481252
}
12491253

1250-
dataFileInfo = (Hashtable) result;
1254+
dataFileInfo = (Hashtable)result;
12511255
error = null;
12521256
return true;
12531257
}
@@ -1369,10 +1373,10 @@ public static bool TryCreateModuleSpecification(
13691373
validatedModuleSpecs = Array.Empty<ModuleSpecification>();
13701374
List<ModuleSpecification> moduleSpecsList = new List<ModuleSpecification>();
13711375

1372-
foreach(Hashtable moduleSpec in moduleSpecHashtables)
1376+
foreach (Hashtable moduleSpec in moduleSpecHashtables)
13731377
{
13741378
// ModuleSpecification(string) constructor for creating a ModuleSpecification when only ModuleName is provided.
1375-
if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"]))
1379+
if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string)moduleSpec["ModuleName"]))
13761380
{
13771381
errorList.Add(new ErrorRecord(
13781382
new ArgumentException($"RequiredModules Hashtable entry {moduleSpec.ToString()} is missing a key 'ModuleName' and associated value, which is required for each module specification entry"),
@@ -1384,7 +1388,7 @@ public static bool TryCreateModuleSpecification(
13841388
}
13851389

13861390
// At this point it must contain ModuleName key.
1387-
string moduleSpecName = (string) moduleSpec["ModuleName"];
1391+
string moduleSpecName = (string)moduleSpec["ModuleName"];
13881392
ModuleSpecification currentModuleSpec = null;
13891393
if (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion"))
13901394
{
@@ -1410,10 +1414,10 @@ public static bool TryCreateModuleSpecification(
14101414
else
14111415
{
14121416
// ModuleSpecification(Hashtable) constructor for when ModuleName + {Required,Maximum,Module}Version value is also provided.
1413-
string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaximumVersion"] : String.Empty;
1414-
string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty;
1415-
string moduleSpecRequiredVersion = moduleSpec.ContainsKey("RequiredVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty;
1416-
Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty;
1417+
string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string)moduleSpec["MaximumVersion"] : String.Empty;
1418+
string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string)moduleSpec["ModuleVersion"] : String.Empty;
1419+
string moduleSpecRequiredVersion = moduleSpec.ContainsKey("RequiredVersion") ? (string)moduleSpec["RequiredVersion"] : String.Empty;
1420+
Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid)moduleSpec["Guid"] : Guid.Empty;
14171421

14181422
if (String.IsNullOrEmpty(moduleSpecMaxVersion) && String.IsNullOrEmpty(moduleSpecModuleVersion) && String.IsNullOrEmpty(moduleSpecRequiredVersion))
14191423
{
@@ -1536,22 +1540,36 @@ public static void DeleteDirectoryWithRestore(string dirPath)
15361540
/// </Summary>
15371541
public static void DeleteDirectory(string dirPath)
15381542
{
1539-
foreach (var dirFilePath in Directory.GetFiles(dirPath))
1543+
// Remove read only file attributes first
1544+
foreach (var dirFilePath in Directory.GetFiles(dirPath,"*",SearchOption.AllDirectories))
15401545
{
15411546
if (File.GetAttributes(dirFilePath).HasFlag(FileAttributes.ReadOnly))
15421547
{
1543-
File.SetAttributes(dirFilePath, (File.GetAttributes(dirFilePath) & ~FileAttributes.ReadOnly));
1548+
File.SetAttributes(dirFilePath, File.GetAttributes(dirFilePath) & ~FileAttributes.ReadOnly);
15441549
}
1545-
1546-
File.Delete(dirFilePath);
15471550
}
1548-
1549-
foreach (var dirSubPath in Directory.GetDirectories(dirPath))
1551+
// Delete directory recursive, try multiple times before throwing ( #1662 )
1552+
int maxAttempts = 5;
1553+
int msDelay = 5;
1554+
for (int attempt = 1; attempt <= maxAttempts; ++attempt)
15501555
{
1551-
DeleteDirectory(dirSubPath);
1556+
try
1557+
{
1558+
Directory.Delete(dirPath,true);
1559+
return;
1560+
}
1561+
catch (Exception ex)
1562+
{
1563+
if (attempt < maxAttempts && (ex is IOException || ex is UnauthorizedAccessException))
1564+
{
1565+
Thread.Sleep(msDelay);
1566+
}
1567+
else
1568+
{
1569+
throw;
1570+
}
1571+
}
15521572
}
1553-
1554-
Directory.Delete(dirPath);
15551573
}
15561574

15571575
/// <Summary>

0 commit comments

Comments
 (0)