diff --git a/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs b/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs
index dc26ebeb9ba..6c6511693bf 100644
--- a/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs
+++ b/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs
@@ -1217,6 +1217,8 @@ public sealed partial class Unzip : Microsoft.Build.Tasks.TaskExtension, Microso
public Unzip() { }
[Microsoft.Build.Framework.RequiredAttribute]
public Microsoft.Build.Framework.ITaskItem DestinationFolder { get { throw null; } set { } }
+ public string Exclude { get { throw null; } set { } }
+ public string Include { get { throw null; } set { } }
public bool OverwriteReadOnlyFiles { get { throw null; } set { } }
public bool SkipUnchangedFiles { get { throw null; } set { } }
[Microsoft.Build.Framework.RequiredAttribute]
diff --git a/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs b/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs
index 0d85a2cc928..e788b3e532f 100644
--- a/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs
+++ b/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs
@@ -894,6 +894,8 @@ public sealed partial class Unzip : Microsoft.Build.Tasks.TaskExtension, Microso
public Unzip() { }
[Microsoft.Build.Framework.RequiredAttribute]
public Microsoft.Build.Framework.ITaskItem DestinationFolder { get { throw null; } set { } }
+ public string Exclude { get { throw null; } set { } }
+ public string Include { get { throw null; } set { } }
public bool OverwriteReadOnlyFiles { get { throw null; } set { } }
public bool SkipUnchangedFiles { get { throw null; } set { } }
[Microsoft.Build.Framework.RequiredAttribute]
diff --git a/src/Shared/FileMatcher.cs b/src/Shared/FileMatcher.cs
index a9c8fa2b2f3..0b187644116 100644
--- a/src/Shared/FileMatcher.cs
+++ b/src/Shared/FileMatcher.cs
@@ -31,6 +31,8 @@ internal class FileMatcher
private static readonly char[] s_wildcardCharacters = { '*', '?' };
private static readonly char[] s_wildcardAndSemicolonCharacters = { '*', '?', ';' };
+ private static readonly string[] s_propertyAndItemReferences = { "$(", "@(" };
+
// on OSX both System.IO.Path separators are '/', so we have to use the literals
internal static readonly char[] directorySeparatorCharacters = { '/', '\\' };
internal static readonly string[] directorySeparatorStrings = directorySeparatorCharacters.Select(c => c.ToString()).ToArray();
@@ -166,8 +168,6 @@ internal static void ClearFileEnumerationsCache()
///
/// Determines whether the given path has any wild card characters.
///
- ///
- ///
internal static bool HasWildcards(string filespec)
{
// Perf Note: Doing a [Last]IndexOfAny(...) is much faster than compiling a
@@ -180,18 +180,33 @@ internal static bool HasWildcards(string filespec)
}
///
- /// Determines whether the given path has any wild card characters or any semicolons.
+ /// Determines whether the given path has any wild card characters or semicolons.
+ ///
+ internal static bool HasWildcardsOrSemicolon(string filespec)
+ {
+ return -1 != filespec.LastIndexOfAny(s_wildcardAndSemicolonCharacters);
+ }
+
+ ///
+ /// Determines whether the given path has any wild card characters, any semicolons or any property references.
///
internal static bool HasWildcardsSemicolonItemOrPropertyReferences(string filespec)
{
return
(-1 != filespec.IndexOfAny(s_wildcardAndSemicolonCharacters)) ||
- filespec.Contains("$(") ||
- filespec.Contains("@(")
+ HasPropertyOrItemReferences(filespec)
;
}
+ ///
+ /// Determines whether the given path has any property references.
+ ///
+ internal static bool HasPropertyOrItemReferences(string filespec)
+ {
+ return s_propertyAndItemReferences.Any(filespec.Contains);
+ }
+
///
/// Get the files and\or folders specified by the given path and pattern.
///
diff --git a/src/Tasks.UnitTests/Unzip_Tests.cs b/src/Tasks.UnitTests/Unzip_Tests.cs
index 4ccb35c6a2d..8b48e6ccaf8 100644
--- a/src/Tasks.UnitTests/Unzip_Tests.cs
+++ b/src/Tasks.UnitTests/Unzip_Tests.cs
@@ -214,5 +214,212 @@ public void LogsErrorIfSourceFileDoesNotExist()
_mockEngine.Log.ShouldContain("MSB3932", () => _mockEngine.Log);
}
}
+
+ [Fact]
+ public void CanUnzip_WithIncludeFilter()
+ {
+ using (TestEnvironment testEnvironment = TestEnvironment.Create())
+ {
+ TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
+ TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
+ testEnvironment.CreateFile(source, "BE78A17D30144B549D21F71D5C633F7D.txt", "file1");
+ testEnvironment.CreateFile(source, "A04FF4B88DF14860B7C73A8E75A4FB76.txt", "file2");
+
+ TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));
+
+ Unzip unzip = new Unzip
+ {
+ BuildEngine = _mockEngine,
+ DestinationFolder = new TaskItem(destination.Path),
+ OverwriteReadOnlyFiles = true,
+ SkipUnchangedFiles = false,
+ SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
+ Include = "BE78A17D30144B549D21F71D5C633F7D.txt"
+ };
+
+ unzip.Execute().ShouldBeTrue(() => _mockEngine.Log);
+
+ _mockEngine.Log.ShouldContain(Path.Combine(destination.Path, "BE78A17D30144B549D21F71D5C633F7D.txt"), () => _mockEngine.Log);
+ _mockEngine.Log.ShouldNotContain(Path.Combine(destination.Path, "A04FF4B88DF14860B7C73A8E75A4FB76.txt"), () => _mockEngine.Log);
+ }
+ }
+
+ [Fact]
+ public void CanUnzip_WithExcludeFilter()
+ {
+ using (TestEnvironment testEnvironment = TestEnvironment.Create())
+ {
+ TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
+ TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
+ testEnvironment.CreateFile(source, "BE78A17D30144B549D21F71D5C633F7D.txt", "file1");
+ testEnvironment.CreateFile(source, "A04FF4B88DF14860B7C73A8E75A4FB76.txt", "file2");
+
+ TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));
+
+ Unzip unzip = new Unzip
+ {
+ BuildEngine = _mockEngine,
+ DestinationFolder = new TaskItem(destination.Path),
+ OverwriteReadOnlyFiles = true,
+ SkipUnchangedFiles = false,
+ SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
+ Exclude = "BE78A17D30144B549D21F71D5C633F7D.txt"
+ };
+
+ unzip.Execute().ShouldBeTrue(() => _mockEngine.Log);
+
+ _mockEngine.Log.ShouldNotContain(Path.Combine(destination.Path, "BE78A17D30144B549D21F71D5C633F7D.txt"), () => _mockEngine.Log);
+ _mockEngine.Log.ShouldContain(Path.Combine(destination.Path, "A04FF4B88DF14860B7C73A8E75A4FB76.txt"), () => _mockEngine.Log);
+ }
+ }
+
+ [Fact]
+ public void CanUnzip_WithIncludeAndExcludeFilter()
+ {
+ using (TestEnvironment testEnvironment = TestEnvironment.Create())
+ {
+ TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
+ TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
+ TransientTestFolder sub = source.CreateDirectory("sub");
+ testEnvironment.CreateFile(source, "file1.js", "file1");
+ testEnvironment.CreateFile(source, "file1.js.map", "file2");
+ testEnvironment.CreateFile(source, "file2.js", "file3");
+ testEnvironment.CreateFile(source, "readme.txt", "file4");
+ testEnvironment.CreateFile(sub, "subfile.js", "File5");
+
+ TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));
+
+ Unzip unzip = new Unzip
+ {
+ BuildEngine = _mockEngine,
+ DestinationFolder = new TaskItem(destination.Path),
+ OverwriteReadOnlyFiles = true,
+ SkipUnchangedFiles = false,
+ SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
+ Include = "*.js",
+ Exclude = "*.js.map;sub\\*.js"
+ };
+
+ unzip.Execute().ShouldBeTrue(() => _mockEngine.Log);
+
+ _mockEngine.Log.ShouldContain(Path.Combine(destination.Path, "file1.js"), () => _mockEngine.Log);
+ _mockEngine.Log.ShouldNotContain(Path.Combine(destination.Path, "file1.js.map"), () => _mockEngine.Log);
+ _mockEngine.Log.ShouldContain(Path.Combine(destination.Path, "file2.js"), () => _mockEngine.Log);
+ _mockEngine.Log.ShouldNotContain(Path.Combine(destination.Path, "readme.txt"), () => _mockEngine.Log);
+ _mockEngine.Log.ShouldNotContain(Path.Combine(destination.Path, "sub", "subfile.js"), () => _mockEngine.Log);
+ }
+ }
+
+ [Fact]
+ public void LogsErrorIfIncludeContainsInvalidPathCharacters()
+ {
+ using (TestEnvironment testEnvironment = TestEnvironment.Create())
+ {
+ TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
+ TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
+ testEnvironment.CreateFile(source, "BE78A17D30144B549D21F71D5C633F7D.txt", "file1");
+ testEnvironment.CreateFile(source, "A04FF4B88DF14860B7C73A8E75A4FB76.txt", "file2");
+
+ TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));
+
+ Unzip unzip = new Unzip
+ {
+ BuildEngine = _mockEngine,
+ DestinationFolder = new TaskItem(destination.Path),
+ OverwriteReadOnlyFiles = true,
+ SkipUnchangedFiles = false,
+ SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
+ Include = " _mockEngine.Log);
+
+ _mockEngine.Log.ShouldContain("MSB3937", () => _mockEngine.Log);
+ }
+ }
+
+ [Fact]
+ public void LogsErrorIfIncludeContainsPropertyReferences()
+ {
+ using (TestEnvironment testEnvironment = TestEnvironment.Create())
+ {
+ TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
+ TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
+ testEnvironment.CreateFile(source, "BE78A17D30144B549D21F71D5C633F7D.txt", "file1");
+ testEnvironment.CreateFile(source, "A04FF4B88DF14860B7C73A8E75A4FB76.txt", "file2");
+
+ TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));
+
+ Unzip unzip = new Unzip
+ {
+ BuildEngine = _mockEngine,
+ DestinationFolder = new TaskItem(destination.Path),
+ OverwriteReadOnlyFiles = true,
+ SkipUnchangedFiles = false,
+ SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
+ Include = "$(Include)"
+ };
+
+ unzip.Execute().ShouldBeFalse(() => _mockEngine.Log);
+
+ _mockEngine.Log.ShouldContain("MSB3938", () => _mockEngine.Log);
+ }
+ }
+
+ [Fact]
+ public void LogsErrorIfExcludeContainsInvalidPathCharacters()
+ {
+ using (TestEnvironment testEnvironment = TestEnvironment.Create())
+ {
+ TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
+ TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
+ testEnvironment.CreateFile(source, "BE78A17D30144B549D21F71D5C633F7D.txt", "file1");
+ testEnvironment.CreateFile(source, "A04FF4B88DF14860B7C73A8E75A4FB76.txt", "file2");
+
+ TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));
+
+ Unzip unzip = new Unzip
+ {
+ BuildEngine = _mockEngine,
+ DestinationFolder = new TaskItem(destination.Path),
+ OverwriteReadOnlyFiles = true,
+ SkipUnchangedFiles = false,
+ SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
+ Exclude = " _mockEngine.Log);
+
+ _mockEngine.Log.ShouldContain("MSB3937", () => _mockEngine.Log);
+ }
+ }
+
+ [Fact]
+ public void LogsErrorIfExcludeContainsPropertyReferences()
+ {
+ using (TestEnvironment testEnvironment = TestEnvironment.Create())
+ {
+ TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
+ TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
+ testEnvironment.CreateFile(source, "BE78A17D30144B549D21F71D5C633F7D.txt", "file1");
+ testEnvironment.CreateFile(source, "A04FF4B88DF14860B7C73A8E75A4FB76.txt", "file2");
+
+ TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));
+
+ Unzip unzip = new Unzip
+ {
+ BuildEngine = _mockEngine,
+ DestinationFolder = new TaskItem(destination.Path),
+ OverwriteReadOnlyFiles = true,
+ SkipUnchangedFiles = false,
+ SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
+ Exclude = "$(Include)"
+ };
+
+ unzip.Execute().ShouldBeFalse(() => _mockEngine.Log);
+
+ _mockEngine.Log.ShouldContain("MSB3938", () => _mockEngine.Log);
+ }
+ }
}
}
diff --git a/src/Tasks/Resources/Strings.resx b/src/Tasks/Resources/Strings.resx
index b465dd10789..a054ea6c65a 100644
--- a/src/Tasks/Resources/Strings.resx
+++ b/src/Tasks/Resources/Strings.resx
@@ -2789,9 +2789,20 @@
MSB3936: Failed to open unzip file "{0}" to "{1}". {2}{StrBegin="MSB3936: "}
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+
Did not unzip from file "{0}" to file "{1}" because the "{2}" parameter was set to "{3}" in the project and the files' sizes and timestamps match.
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
Unzipping file "{0}" to "{1}".
diff --git a/src/Tasks/Resources/xlf/Strings.cs.xlf b/src/Tasks/Resources/xlf/Strings.cs.xlf
index d63041a77a8..c6ebbc2bf98 100644
--- a/src/Tasks/Resources/xlf/Strings.cs.xlf
+++ b/src/Tasks/Resources/xlf/Strings.cs.xlf
@@ -2480,6 +2480,11 @@
Rozzipování ze souboru {0} do souboru {1} neproběhlo, protože parametr {2} byl v projektu nastaven na hodnotu {3} a velikosti souborů a časová razítka se shodují.
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: Rozzipování do adresáře {0} se nepodařilo, protože ho nebylo možné vytvořit. {1}
@@ -2510,6 +2515,16 @@
MSB3932: Soubor {0} se nepodařilo rozzipovat, protože neexistuje nebo není přístupný.{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ Soubor {0} se rozzipovává do {1}.
diff --git a/src/Tasks/Resources/xlf/Strings.de.xlf b/src/Tasks/Resources/xlf/Strings.de.xlf
index 6f55e8ffb7d..9d261b2b46f 100644
--- a/src/Tasks/Resources/xlf/Strings.de.xlf
+++ b/src/Tasks/Resources/xlf/Strings.de.xlf
@@ -2480,6 +2480,11 @@
Die Datei "{0}" wurde nicht in die Datei "{1}" entzippt, weil der Parameter "{2}" im Projekt auf "{3}" festgelegt war und die Größen und Zeitstempel der Dateien übereinstimmen.
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: Fehler beim Entzippen in das Verzeichnis "{0}", weil dieses nicht erstellt werden konnte. {1}
@@ -2510,6 +2515,16 @@
MSB3932: Die Datei "{0}" konnte nicht entzippt werden, weil sie nicht vorhanden ist oder nicht darauf zugegriffen werden kann.{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ Die Datei "{0}" wird in "{1}" entzippt.
diff --git a/src/Tasks/Resources/xlf/Strings.en.xlf b/src/Tasks/Resources/xlf/Strings.en.xlf
index 0c447b05ab3..df627557c4a 100644
--- a/src/Tasks/Resources/xlf/Strings.en.xlf
+++ b/src/Tasks/Resources/xlf/Strings.en.xlf
@@ -2530,6 +2530,11 @@
Did not unzip from file "{0}" to file "{1}" because the "{2}" parameter was set to "{3}" in the project and the files' sizes and timestamps match.
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: Failed to unzip to directory "{0}" because it could not be created. {1}
@@ -2560,6 +2565,16 @@
MSB3932: Failed to unzip file "{0}" because the file does not exist or is inaccessible.{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ Unzipping file "{0}" to "{1}".
diff --git a/src/Tasks/Resources/xlf/Strings.es.xlf b/src/Tasks/Resources/xlf/Strings.es.xlf
index 620929ddfe8..8a28657818f 100644
--- a/src/Tasks/Resources/xlf/Strings.es.xlf
+++ b/src/Tasks/Resources/xlf/Strings.es.xlf
@@ -2480,6 +2480,11 @@
No se descomprimió del archivo "{0}" en el archivo "{1}" porque el parámetro "{2}" se estableció como "{3}" en el proyecto y los tamaños y las marcas de tiempo de los archivos coinciden.
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: No se pudo descomprimir en el directorio "{0}" porque no se pudo crear. {1}
@@ -2510,6 +2515,16 @@
MSB3932: No se pudo descomprimir el archivo "{0}" porque no existe o no se puede tener acceso a él.{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ Descomprimiendo el archivo "{0}" en "{1}".
diff --git a/src/Tasks/Resources/xlf/Strings.fr.xlf b/src/Tasks/Resources/xlf/Strings.fr.xlf
index 11359a59620..3854fcf0c19 100644
--- a/src/Tasks/Resources/xlf/Strings.fr.xlf
+++ b/src/Tasks/Resources/xlf/Strings.fr.xlf
@@ -2480,6 +2480,11 @@
Impossible de décompresser le fichier "{0}" vers le fichier "{1}", car le paramètre "{2}" a la valeur "{3}" dans le projet, et les tailles et horodatages des fichiers correspondent.
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: Échec de la décompression dans le répertoire "{0}", car il n'a pas pu être créé. {1}
@@ -2510,6 +2515,16 @@
MSB3932: Échec de la décompression du fichier "{0}", car le fichier n'existe pas ou est inaccessible.{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ Décompression du fichier "{0}" dans "{1}".
diff --git a/src/Tasks/Resources/xlf/Strings.it.xlf b/src/Tasks/Resources/xlf/Strings.it.xlf
index 39b30cfbacb..14bdb0e8baf 100644
--- a/src/Tasks/Resources/xlf/Strings.it.xlf
+++ b/src/Tasks/Resources/xlf/Strings.it.xlf
@@ -2480,6 +2480,11 @@
Non è stato possibile decomprimere il file "{0}" nel file "{1}". Il parametro "{2}" è stato impostato su "{3}" nel progetto e le dimensioni e il timestamp dei file corrispondono.
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: non è stato possibile decomprimere nella directory "{0}" perché non è stato possibile crearla. {1}
@@ -2510,6 +2515,16 @@
MSB3932: non è stato possibile decomprimere il file "{0}" perché non esiste oppure è inaccessibile.{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ Decompressione del file "{0}" in "{1}".
diff --git a/src/Tasks/Resources/xlf/Strings.ja.xlf b/src/Tasks/Resources/xlf/Strings.ja.xlf
index 8166fd4d2dd..a4b07fadb16 100644
--- a/src/Tasks/Resources/xlf/Strings.ja.xlf
+++ b/src/Tasks/Resources/xlf/Strings.ja.xlf
@@ -2480,6 +2480,11 @@
"{2}" パラメーターがプロジェクトで "{3}" に設定されているため、またファイルのサイズとタイムスタンプが一致するため、ファイル "{0}" からファイル "{1}" に解凍しませんでした。
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: ディレクトリ "{0}" への解凍は、そのディレクトリを作成できなかったため、失敗しました。{1}
@@ -2510,6 +2515,16 @@
MSB3932: ファイルが存在しないか、アクセスできないため、ファイル "{0}" を解凍できませんでした。{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ ファイル "{0}" を "{1}" に解凍しています。
diff --git a/src/Tasks/Resources/xlf/Strings.ko.xlf b/src/Tasks/Resources/xlf/Strings.ko.xlf
index dd566fd77b2..e801bdd91a6 100644
--- a/src/Tasks/Resources/xlf/Strings.ko.xlf
+++ b/src/Tasks/Resources/xlf/Strings.ko.xlf
@@ -2480,6 +2480,11 @@
"{2}" 매개 변수가 프로젝트에 "{3}"(으)로 설정되었고 파일 크기와 타임스탬프가 일치하기 때문에 "{0}" 파일에서 "{1}" 파일로 압축을 풀 수 없습니다.
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: "{0}" 디렉터리를 생성할 수 없기 때문에 이 디렉터리에 압축을 풀지 못했습니다. {1}
@@ -2510,6 +2515,16 @@
MSB3932: 파일이 존재하지 않거나 액세스할 수 없기 때문에 파일 "{0}"의 압축을 풀지 못했습니다.{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ 파일 "{0}"의 압축을 "{1}"에 푸는 중입니다.
diff --git a/src/Tasks/Resources/xlf/Strings.pl.xlf b/src/Tasks/Resources/xlf/Strings.pl.xlf
index bf99b39d5ea..4251be9ff36 100644
--- a/src/Tasks/Resources/xlf/Strings.pl.xlf
+++ b/src/Tasks/Resources/xlf/Strings.pl.xlf
@@ -2480,6 +2480,11 @@
Nie wykonano rozpakowywania z pliku „{0}” do pliku „{1}”, ponieważ parametr „{2}” w projekcie został ustawiony na wartość „{3}”, a rozmiary plików i sygnatury czasowe pasują do siebie.
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: Nie można rozpakować do katalogu „{0}”, ponieważ nie można go utworzyć. {1}
@@ -2510,6 +2515,16 @@
MSB3932: Nie można rozpakować pliku „{0}”, ponieważ plik nie istnieje lub jest niedostępny.{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ Rozpakowywanie pliku „{0}” do pliku „{1}”.
diff --git a/src/Tasks/Resources/xlf/Strings.pt-BR.xlf b/src/Tasks/Resources/xlf/Strings.pt-BR.xlf
index dc823788e5c..1dfff7329b4 100644
--- a/src/Tasks/Resources/xlf/Strings.pt-BR.xlf
+++ b/src/Tasks/Resources/xlf/Strings.pt-BR.xlf
@@ -2480,6 +2480,11 @@
Não foi possível descompactar o arquivo "{0}" para o arquivo "{1}", pois o parâmetro "{2}" foi definido como "{3}" no projeto, e os tamanhos de arquivos e os carimbos de data/hora não correspondem.
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: Falha ao descompactar no diretório "{0}" porque ele não pôde ser criado. {1}
@@ -2510,6 +2515,16 @@
MSB3932: Falha ao descompactar o arquivo "{0}" porque ele não existe ou está inacessível.{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ Descompactando o arquivo "{0}" em "{1}".
diff --git a/src/Tasks/Resources/xlf/Strings.ru.xlf b/src/Tasks/Resources/xlf/Strings.ru.xlf
index 9e7588f33eb..9e883f29af7 100644
--- a/src/Tasks/Resources/xlf/Strings.ru.xlf
+++ b/src/Tasks/Resources/xlf/Strings.ru.xlf
@@ -2480,6 +2480,11 @@
Не удалось выполнить распаковку из файла "{0}" в файл "{1}", так как для параметра "{2}" в проекте было задано значение "{3}", а размеры файлов и отметки времени совпадают.
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: не удалось выполнить распаковку в каталог "{0}", так как создать его не удалось. {1}
@@ -2510,6 +2515,16 @@
MSB3932: не удалось распаковать файл "{0}", так как он не существует или недоступен.{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ Распаковка файла "{0}" в"{1}".
diff --git a/src/Tasks/Resources/xlf/Strings.tr.xlf b/src/Tasks/Resources/xlf/Strings.tr.xlf
index f4e09678061..194e8b3f3e1 100644
--- a/src/Tasks/Resources/xlf/Strings.tr.xlf
+++ b/src/Tasks/Resources/xlf/Strings.tr.xlf
@@ -2480,6 +2480,11 @@
Projede "{2}" parametresi "{3}" olarak ayarlandığından ve dosya boyutlarıyla zaman damgaları eşleştiğinden "{0}" dosyasını "{1}" dosyasına çıkarma işlemi gerçekleştirilmedi.
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: "{0}" dizini oluşturulamadığından bu dizine çıkarılamadı. {1}
@@ -2510,6 +2515,16 @@
MSB3932: Dosya mevcut olmadığından veya erişilebilir olmadığından "{0}" dosyasının sıkıştırması açılamadı.{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ "{0}" dosyasının sıkıştırması "{1}" hedefine açılıyor.
diff --git a/src/Tasks/Resources/xlf/Strings.zh-Hans.xlf b/src/Tasks/Resources/xlf/Strings.zh-Hans.xlf
index 37aa968f49b..44e2fce72d4 100644
--- a/src/Tasks/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/Tasks/Resources/xlf/Strings.zh-Hans.xlf
@@ -2480,6 +2480,11 @@
未从文件“{0}”解压缩到文件“{1}”,因为“{2}”参数在项目中设置为“{3}”,而两个文件的大小及时间戳一致。
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: 未能解压缩到目录“{0}”,因为无法创建它。{1}
@@ -2510,6 +2515,16 @@
MSB3932: 未能解压缩文件“{0}”,因为该文件不存在或无法访问。{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ 将文件“{0}”解压缩到“{1}”。
diff --git a/src/Tasks/Resources/xlf/Strings.zh-Hant.xlf b/src/Tasks/Resources/xlf/Strings.zh-Hant.xlf
index 2fa9517589f..a70ec8fb257 100644
--- a/src/Tasks/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/Tasks/Resources/xlf/Strings.zh-Hant.xlf
@@ -2480,6 +2480,11 @@
並未從檔案 "{0}" 解壓縮到檔案 "{1}",因為在專案中的 "{2}" 參數原先設定為 "{3}",且檔案的大小與時間戳記相符。
+
+
+ Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.
+
+ MSB3931: 因為無法建立目錄 "{0}",所以無法解壓縮至該目錄。{1}
@@ -2510,6 +2515,16 @@
MSB3932: 因為檔案不存在或無法存取,所以無法解壓縮檔案 "{0}"。{StrBegin="MSB3932: "}
+
+
+ MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.
+ {StrBegin="MSB3937: "}
+
+
+
+ MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.
+ {StrBegin="MSB3938: "}
+ 正在將檔案 "{0}" 解壓縮到 "{1}"。
diff --git a/src/Tasks/Unzip.cs b/src/Tasks/Unzip.cs
index 401829e2f65..4e00a677831 100644
--- a/src/Tasks/Unzip.cs
+++ b/src/Tasks/Unzip.cs
@@ -1,14 +1,16 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading;
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
+using Microsoft.Build.Utilities;
namespace Microsoft.Build.Tasks
{
@@ -27,6 +29,16 @@ public sealed class Unzip : TaskExtension, ICancelableTask
///
private readonly CancellationTokenSource _cancellationToken = new CancellationTokenSource();
+ ///
+ /// Stores the include patterns after parsing.
+ ///
+ private string[] _includePatterns;
+
+ ///
+ /// Stores the exclude patterns after parsing.
+ ///
+ private string[] _excludePatterns;
+
///
/// Gets or sets a with a destination folder path to unzip the files to.
///
@@ -49,6 +61,16 @@ public sealed class Unzip : TaskExtension, ICancelableTask
[Required]
public ITaskItem[] SourceFiles { get; set; }
+ ///
+ /// Gets or sets an MSBuild glob expression that will be used to determine which files to include being unzipped from the archive.
+ ///
+ public string Include { get; set; }
+
+ ///
+ /// Gets or sets an MSBuild glob expression that will be used to determine which files to exclude from being unzipped from the archive.
+ ///
+ public string Exclude { get; set; }
+
///
public void Cancel()
{
@@ -74,41 +96,46 @@ public override bool Execute()
try
{
- foreach (ITaskItem sourceFile in SourceFiles.TakeWhile(i => !_cancellationToken.IsCancellationRequested))
+ ParseIncludeExclude();
+
+ if (!Log.HasLoggedErrors)
{
- if (!FileSystems.Default.FileExists(sourceFile.ItemSpec))
+ foreach (ITaskItem sourceFile in SourceFiles.TakeWhile(i => !_cancellationToken.IsCancellationRequested))
{
- Log.LogErrorWithCodeFromResources("Unzip.ErrorFileDoesNotExist", sourceFile.ItemSpec);
- continue;
- }
+ if (!FileSystems.Default.FileExists(sourceFile.ItemSpec))
+ {
+ Log.LogErrorWithCodeFromResources("Unzip.ErrorFileDoesNotExist", sourceFile.ItemSpec);
+ continue;
+ }
- try
- {
- using (FileStream stream = new FileStream(sourceFile.ItemSpec, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 0x1000, useAsync: false))
+ try
{
- using (ZipArchive zipArchive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: false))
+ using (FileStream stream = new FileStream(sourceFile.ItemSpec, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 0x1000, useAsync: false))
{
- try
- {
- Extract(zipArchive, destinationDirectory);
- }
- catch (Exception e)
+ using (ZipArchive zipArchive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: false))
{
- // Unhandled exception in Extract() is a bug!
- Log.LogErrorFromException(e, showStackTrace: true);
- return false;
+ try
+ {
+ Extract(zipArchive, destinationDirectory);
+ }
+ catch (Exception e)
+ {
+ // Unhandled exception in Extract() is a bug!
+ Log.LogErrorFromException(e, showStackTrace: true);
+ return false;
+ }
}
}
}
- }
- catch (OperationCanceledException)
- {
- break;
- }
- catch (Exception e)
- {
- // Should only be thrown if the archive could not be opened (Access denied, corrupt file, etc)
- Log.LogErrorWithCodeFromResources("Unzip.ErrorCouldNotOpenFile", sourceFile.ItemSpec, e.Message);
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch (Exception e)
+ {
+ // Should only be thrown if the archive could not be opened (Access denied, corrupt file, etc)
+ Log.LogErrorWithCodeFromResources("Unzip.ErrorCouldNotOpenFile", sourceFile.ItemSpec, e.Message);
+ }
}
}
}
@@ -129,6 +156,12 @@ private void Extract(ZipArchive sourceArchive, DirectoryInfo destinationDirector
{
foreach (ZipArchiveEntry zipArchiveEntry in sourceArchive.Entries.TakeWhile(i => !_cancellationToken.IsCancellationRequested))
{
+ if (ShouldSkipEntry(zipArchiveEntry))
+ {
+ Log.LogMessageFromResources(MessageImportance.Low, "Unzip.DidNotUnzipBecauseOfFilter", zipArchiveEntry.FullName);
+ continue;
+ }
+
FileInfo destinationPath = new FileInfo(Path.Combine(destinationDirectory.FullName, zipArchiveEntry.FullName));
// Zip archives can have directory entries listed explicitly.
@@ -199,6 +232,28 @@ private void Extract(ZipArchive sourceArchive, DirectoryInfo destinationDirector
}
}
+ ///
+ /// Determines whether or not a file should be skipped when unzipping by filtering.
+ ///
+ /// The object containing information about the file in the zip archive.
+ /// true if the file should be skipped, otherwise false.
+ private bool ShouldSkipEntry(ZipArchiveEntry zipArchiveEntry)
+ {
+ bool result = false;
+
+ if (_includePatterns.Length > 0)
+ {
+ result = _includePatterns.All(pattern => !FileMatcher.IsMatch(FileMatcher.Normalize(zipArchiveEntry.FullName), pattern, true));
+ }
+
+ if (_excludePatterns.Length > 0)
+ {
+ result |= _excludePatterns.Any(pattern => FileMatcher.IsMatch(FileMatcher.Normalize(zipArchiveEntry.FullName), pattern, true));
+ }
+
+ return result;
+ }
+
///
/// Determines whether or not a file should be skipped when unzipping.
///
@@ -212,5 +267,34 @@ private bool ShouldSkipEntry(ZipArchiveEntry zipArchiveEntry, FileInfo fileInfo)
&& zipArchiveEntry.LastWriteTime == fileInfo.LastWriteTimeUtc
&& zipArchiveEntry.Length == fileInfo.Length;
}
+
+ private void ParseIncludeExclude()
+ {
+ ParsePattern(Include, out _includePatterns);
+ ParsePattern(Exclude, out _excludePatterns);
+ }
+
+ private void ParsePattern(string pattern, out string[] patterns)
+ {
+ patterns = Array.Empty();
+ if (!string.IsNullOrWhiteSpace(pattern))
+ {
+ if (FileMatcher.HasPropertyOrItemReferences(pattern))
+ {
+ // Supporting property references would require access to Expander which is unavailable in Microsoft.Build.Tasks
+ Log.LogErrorWithCodeFromResources("Unzip.ErrorParsingPatternPropertyReferences", pattern);
+ }
+ else if (pattern.IndexOfAny(FileUtilities.InvalidPathChars) != -1)
+ {
+ Log.LogErrorWithCodeFromResources("Unzip.ErrorParsingPatternInvalidPath", pattern);
+ }
+ else
+ {
+ patterns = pattern.Contains(';')
+ ? pattern.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(FileMatcher.Normalize).ToArray()
+ : new[] { pattern };
+ }
+ }
+ }
}
}