Skip to content

Commit cc8d04e

Browse files
committed
Allow non-existent paths to be removed
Also allows non-existent paths to be added to the source list using -Force. Resolves issue #21.
1 parent d3e93dd commit cc8d04e

File tree

6 files changed

+6765
-39
lines changed

6 files changed

+6765
-39
lines changed

src/PowerShell/Help.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,6 +1281,9 @@
12811281
<parameter name="LiteralPath">
12821282
The directory or URL to register. The value of -LiteralPath is used exactly as typed. No characters are interpreted as wildcards.
12831283
</parameter>
1284+
<parameter name="Force">
1285+
Whether to validate that directories exist before they are added to the source list.
1286+
</parameter>
12841287
<parameter name="PassThru">
12851288
Whether to return the remaining registered source through the pipeline.
12861289
</parameter>

src/PowerShell/Microsoft.Tools.WindowsInstaller.PowerShell.dll-Help.xml

Lines changed: 6609 additions & 1 deletion
Large diffs are not rendered by default.

src/PowerShell/PowerShell/Commands/AddSourceCommand.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,23 @@ namespace Microsoft.Tools.WindowsInstaller.PowerShell.Commands
3333
public sealed class AddSourceCommand : SourcePathCommandBase
3434
{
3535
private static readonly string ErrorId = "DirectoryNotFound";
36+
private bool shouldExist = true;
37+
38+
/// <summary>
39+
/// Gets or sets whether to validate that directories exist before they are added to the source list.
40+
/// </summary>
41+
[Parameter]
42+
public SwitchParameter Force
43+
{
44+
get { return !this.shouldExist; }
45+
set { this.shouldExist = !value; }
46+
}
47+
48+
protected override bool ShouldExist
49+
{
50+
get { return this.shouldExist; }
51+
set { this.shouldExist = value; }
52+
}
3653

3754
/// <summary>
3855
/// Registers a source path to a product or patch.
@@ -46,7 +63,7 @@ protected override void EndProcessing()
4663
{
4764
foreach (var path in param.Paths)
4865
{
49-
if (this.Validate(path))
66+
if (this.Force || this.Validate(path))
5067
{
5168
installation.SourceList.Add(path);
5269
}

src/PowerShell/PowerShell/Commands/SourcePathCommandBase.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
// SOFTWARE.
2222

23+
using System.Collections.Generic;
2324
using System.Management.Automation;
2425

2526
namespace Microsoft.Tools.WindowsInstaller.PowerShell.Commands
@@ -53,20 +54,33 @@ public string[] LiteralPath
5354
[Parameter]
5455
public SwitchParameter PassThru { get; set; }
5556

57+
/// <summary>
58+
/// Gets or sets a value indicating whether the <see cref="Path"/> or <see cref="LiteralPath"/> must exist.
59+
/// </summary>
60+
protected virtual bool ShouldExist { get; set; }
61+
5662
/// <summary>
5763
/// Adds the resolved path to the <see cref="SourceCommandBase.Parameters"/>.
5864
/// </summary>
5965
/// <param name="param">The <see cref="SourceCommandBase.Parameters"/> to update.</param>
6066
protected override void UpdateParameters(Parameters param)
6167
{
62-
var items = this.InvokeProvider.Item.Get(this.Path, true, ParameterSet.LiteralPath == this.ParameterSetName);
63-
64-
foreach (var item in items)
68+
IEnumerable<string> paths;
69+
if (this.ShouldExist)
6570
{
66-
var path = item.GetPropertyValue<string>("PSPath");
67-
path = this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path);
71+
paths = this.InvokeProvider.Item
72+
.Get(this.Path, true, ParameterSet.LiteralPath == this.ParameterSetName)
73+
.Select(item => item.GetPropertyValue<string>("PSPath"));
74+
}
75+
else
76+
{
77+
paths = this.Path;
78+
}
6879

69-
param.Paths.Add(path);
80+
foreach (var path in paths)
81+
{
82+
var providerPath = this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path);
83+
param.Paths.Add(providerPath);
7084
}
7185
}
7286
}

test/PowerShell.Test/PowerShell/Commands/SourceCommandTests.cs

Lines changed: 112 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
using System.Management.Automation;
2525
using Microsoft.Deployment.WindowsInstaller;
2626
using Microsoft.VisualStudio.TestTools.UnitTesting;
27+
using System;
28+
using System.Collections.Generic;
2729

2830
namespace Microsoft.Tools.WindowsInstaller.PowerShell.Commands
2931
{
@@ -41,35 +43,15 @@ public class SourceCommandTests : TestBase
4143
[TestMethod]
4244
public void ModifyProductSource()
4345
{
44-
PSObject obj = null;
45-
46-
// First attempt to see if there are any unmanaged products installed.
47-
using (var p = CreatePipeline("get-msiproductinfo -context userunmanaged"))
48-
{
49-
var output = p.Invoke();
50-
if (null != output && 0 < output.Count())
51-
{
52-
obj = output[0];
53-
}
54-
else
55-
{
56-
Assert.Inconclusive("There are no unmanaged products installed with which to test source list modifications.");
57-
}
58-
}
59-
60-
// Retain the original source locations so we can attempt to restore it.
61-
var product = obj.As<ProductInstallation>();
62-
var original = product.SourceList.ToArray();
63-
64-
try
46+
this.TestProductSources((obj, product, original) =>
6547
{
6648
using (var p = CreatePipeline(string.Format(@"$Input | add-msisource -path '{0}' -passthru", this.TestContext.DeploymentDirectory)))
6749
{
6850
p.Input.Write(obj);
6951
var output = p.Invoke();
7052

7153
Assert.IsNotNull(output);
72-
Assert.AreEqual<int>(original.Length + 1, output.Count());
54+
Assert.AreEqual(original.Count + 1, output.Count());
7355
}
7456

7557
using (var p = CreatePipeline(@"$Input | add-msisource -path 'ShouldNotExist.txt' -passthru"))
@@ -89,8 +71,8 @@ public void ModifyProductSource()
8971

9072
// Should return the previous number of source locations we already registered.
9173
Assert.IsNotNull(output);
92-
Assert.AreEqual<int>(original.Length + 1, output.Count());
93-
Assert.AreEqual<int>(1, p.Error.Count);
74+
Assert.AreEqual(original.Count + 1, output.Count());
75+
Assert.AreEqual(1, p.Error.Count);
9476
}
9577

9678
using (var p = CreatePipeline(@"$Input | clear-msisource; $Input | get-msisource"))
@@ -101,17 +83,17 @@ public void ModifyProductSource()
10183
Assert.IsTrue(null == output || 0 == output.Count());
10284
}
10385

104-
var paths = new string[original.Length + 1];
86+
var paths = new string[original.Count + 1];
10587
paths[0] = this.TestContext.DeploymentDirectory;
10688
original.CopyTo(paths, 1);
10789

108-
using (var p = CreatePipeline(string.Format(@"$Input | add-msisource '{0}' -passthru", product.ProductCode)))
90+
using (var p = CreatePipeline(string.Format(@"$Input | add-msisource '{0}' -force -passthru", product.ProductCode)))
10991
{
11092
p.Input.Write(paths, true);
11193
var output = p.Invoke();
11294

11395
Assert.IsNotNull(output);
114-
Assert.AreEqual<int>(paths.Length, output.Count());
96+
Assert.AreEqual(paths.Length, output.Count());
11597
}
11698

11799
using (var p = CreatePipeline(string.Format(@"$Input | remove-msisource -path '{0}' -passthru", this.TestContext.DeploymentDirectory)))
@@ -120,8 +102,110 @@ public void ModifyProductSource()
120102
var output = p.Invoke();
121103

122104
Assert.IsNotNull(output);
123-
Assert.AreEqual<int>(original.Length, output.Count());
105+
Assert.AreEqual(original.Count, output.Count());
106+
}
107+
});
108+
}
109+
110+
[TestMethod]
111+
public void AddSourceTestsPath()
112+
{
113+
this.TestProductSources((obj, product, original) =>
114+
{
115+
using (var p = CreatePipeline(@"$Input | add-msisource -path 'C:\ShouldNotExist\AddSourceTestsPath' -passthru"))
116+
{
117+
p.Input.Write(obj);
118+
119+
try
120+
{
121+
p.Invoke();
122+
Assert.Fail("Expected CmdletInvocationException exception");
123+
}
124+
catch (CmdletInvocationException)
125+
{
126+
return;
127+
}
128+
catch (Exception ex)
129+
{
130+
Assert.Fail("Expected CmdletInvocationException exception; caught {0} exception", ex.GetType().Name);
131+
}
132+
}
133+
});
134+
}
135+
136+
[TestMethod]
137+
public void AddSourceForceNoTestsPath()
138+
{
139+
this.TestProductSources((obj, product, original) =>
140+
{
141+
using (var p = CreatePipeline(@"$Input | add-msisource -path 'C:\ShouldNotExist\AddSourceForceNoTestsPath' -force -passthru"))
142+
{
143+
p.Input.Write(obj);
144+
var output = p.Invoke();
145+
146+
Assert.IsFalse(p.HadErrors);
147+
Assert.IsNotNull(output);
148+
Assert.AreEqual(original.Count + 1, output.Count());
124149
}
150+
});
151+
}
152+
153+
[TestMethod]
154+
public void RemoveSourceNoTestsPath()
155+
{
156+
this.TestProductSources((obj, product, original) =>
157+
{
158+
using (var p = CreatePipeline(@"$Input | remove-msisource -path 'C:\ShouldNotExist\RemoveSourceNoTestsPath' -passthru"))
159+
{
160+
p.Input.Write(obj);
161+
var output = p.Invoke();
162+
163+
Assert.IsFalse(p.HadErrors);
164+
Assert.IsNotNull(output);
165+
Assert.AreEqual(original.Count, output.Count());
166+
}
167+
});
168+
}
169+
170+
private PSObject FindTestProduct()
171+
{
172+
using (var p = CreatePipeline("get-msiproductinfo -context userunmanaged"))
173+
{
174+
var output = p.Invoke();
175+
if (null != output && 0 < output.Count())
176+
{
177+
return output[0];
178+
}
179+
}
180+
181+
return null;
182+
}
183+
184+
private void TestProductSources(Action<PSObject, ProductInstallation, ICollection<string>> action)
185+
{
186+
PSObject obj = null;
187+
188+
// First attempt to see if there are any unmanaged products installed.
189+
using (var p = CreatePipeline("get-msiproductinfo -context userunmanaged"))
190+
{
191+
var output = p.Invoke();
192+
if (null != output && 0 < output.Count())
193+
{
194+
obj = output[0];
195+
}
196+
else
197+
{
198+
Assert.Inconclusive("There are no unmanaged products installed with which to test source list modifications.");
199+
}
200+
}
201+
202+
// Retain the original source locations so we can attempt to restore it.
203+
var product = obj.As<ProductInstallation>();
204+
var original = product.SourceList.ToArray();
205+
206+
try
207+
{
208+
action(obj, product, original);
125209
}
126210
finally
127211
{

test/PowerShell.Test/RegistryXml.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ internal RegistryXml(RegistryKey key)
7373
}
7474

7575
/// <summary>
76-
/// Gets the propety dictionary.
76+
/// Gets the property dictionary.
7777
/// </summary>
7878
internal Dictionary<string, string> Properties { get; private set; }
7979

@@ -82,10 +82,10 @@ internal RegistryXml(RegistryKey key)
8282
/// </summary>
8383
/// <param name="reader">The <see cref="XmlReader"/> that contains the keys and values to import.</param>
8484
/// <exception cref="NotSupportedException">A hive name was specified that is not supported, or a value type was not supported.</exception>
85-
/// <exception cref="XmlException">A general XML exception occured.</exception>
85+
/// <exception cref="XmlException">A general XML exception occurred.</exception>
8686
internal void Import(XmlReader reader)
8787
{
88-
Debug.Assert(null != reader, $@"The argument ""{nameof(reader)}"" is null.");
88+
Debug.Assert(null != reader, @"The argument ""reader"" is null.");
8989

9090
while (reader.Read())
9191
{

0 commit comments

Comments
 (0)