Skip to content

Commit 65c91d5

Browse files
Add FileNameFriendly property to DbaInstanceParameter for safe filenames (#31)
* Add FileNameFriendly property to DbaInstanceParameter for safe filenames Fixes #30 When connecting via named pipes (e.g., .\SQLSERVER), the FullSmoName property returns values like "NP:.$SQLSERVER" which contain colons - invalid characters for Windows filenames. This causes Backup-DbaDbMasterKey to create truncated filenames like "NP" instead of the intended name. This commit adds a new FileNameFriendly property that sanitizes the instance name for use in filenames by replacing invalid characters (colons, backslashes, commas) with underscores. Changes: - Added FileNameFriendly property to DbaInstanceParameter class - Updated existing tests to verify FileNameFriendly output - Added comprehensive TestFileNameFriendly test covering various scenarios The main dbatools repository can now use FileNameFriendly instead of FullSmoName when generating backup filenames. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Chrissy LeMaire <[email protected]> * Enhance FileNameFriendly with comprehensive character sanitization - Use Path.GetInvalidFileNameChars() for comprehensive sanitization - Now handles all invalid filename characters including IPv6 brackets - Improved XML documentation with explicit character list - Added edge case tests for IPv6 and IPv4 with ports - Enhanced existing tests to verify no invalid characters remain Implements code review recommendations from PR #31 Co-authored-by: Chrissy LeMaire <[email protected]> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
1 parent be6429e commit 65c91d5

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

project/dbatools.Tests/Parameter/DbaInstanceParamaterTest.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public void TestDotHostname()
9494
Assert.AreEqual(".", dbaInstanceParamater.FullName);
9595
Assert.IsTrue(dbaInstanceParamater.IsLocalHost);
9696
Assert.AreEqual("NP:.", dbaInstanceParamater.FullSmoName);
97+
Assert.AreEqual("NP_.", dbaInstanceParamater.FileNameFriendly);
9798
Assert.AreEqual(@"MSSQLSERVER", dbaInstanceParamater.InstanceName);
9899
Assert.AreEqual(@"[MSSQLSERVER]", dbaInstanceParamater.SqlInstanceName);
99100
Assert.AreEqual(@"[.]", dbaInstanceParamater.SqlFullName);
@@ -115,6 +116,7 @@ public void TestDotHostnameWithInstance()
115116
Assert.AreEqual(@".\instancename", dbaInstanceParamater.FullName);
116117
Assert.IsTrue(dbaInstanceParamater.IsLocalHost);
117118
Assert.AreEqual(@"NP:.\instancename", dbaInstanceParamater.FullSmoName);
119+
Assert.AreEqual(@"NP_._instancename", dbaInstanceParamater.FileNameFriendly);
118120
Assert.AreEqual(@"instancename", dbaInstanceParamater.InstanceName);
119121
Assert.AreEqual(@"[instancename]", dbaInstanceParamater.SqlInstanceName);
120122
Assert.AreEqual(@"[.\instancename]", dbaInstanceParamater.SqlFullName);
@@ -238,5 +240,81 @@ public void TestServerNameWithDash()
238240
Assert.AreEqual("[My-Instance.domain.local\\MyTestInstance]", dbaInstanceParamater.SqlFullName);
239241
Assert.IsFalse(dbaInstanceParamater.IsConnectionString);
240242
}
243+
244+
/// <summary>
245+
/// Tests that FileNameFriendly returns valid filenames for various connection types
246+
/// </summary>
247+
[TestMethod]
248+
public void TestFileNameFriendly()
249+
{
250+
// Test with named pipes using dot notation
251+
var npDot = new DbaInstanceParameter(".");
252+
Assert.AreEqual("NP_.", npDot.FileNameFriendly);
253+
Assert.IsFalse(npDot.FileNameFriendly.Contains(":"));
254+
255+
// Test with named pipes using dot notation with instance
256+
var npDotInstance = new DbaInstanceParameter(@".\SQLSERVER");
257+
Assert.AreEqual("NP_._SQLSERVER", npDotInstance.FileNameFriendly);
258+
Assert.IsFalse(npDotInstance.FileNameFriendly.Contains(":"));
259+
Assert.IsFalse(npDotInstance.FileNameFriendly.Contains("\\"));
260+
261+
// Test with TCP protocol
262+
var tcpInstance = new DbaInstanceParameter("TCP:server\\instance");
263+
Assert.AreEqual("TCP_server_instance", tcpInstance.FileNameFriendly);
264+
Assert.IsFalse(tcpInstance.FileNameFriendly.Contains(":"));
265+
Assert.IsFalse(tcpInstance.FileNameFriendly.Contains("\\"));
266+
267+
// Test with port number
268+
var withPort = new DbaInstanceParameter("server,1433");
269+
Assert.AreEqual("server_1433", withPort.FileNameFriendly);
270+
Assert.IsFalse(withPort.FileNameFriendly.Contains(","));
271+
272+
// Test with regular instance name (no protocol)
273+
var regular = new DbaInstanceParameter("server\\instance");
274+
Assert.AreEqual("server_instance", regular.FileNameFriendly);
275+
Assert.IsFalse(regular.FileNameFriendly.Contains("\\"));
276+
277+
// Test that all results contain no invalid filename characters
278+
var invalidChars = System.IO.Path.GetInvalidFileNameChars();
279+
Assert.IsFalse(npDot.FileNameFriendly.IndexOfAny(invalidChars) >= 0);
280+
Assert.IsFalse(npDotInstance.FileNameFriendly.IndexOfAny(invalidChars) >= 0);
281+
Assert.IsFalse(tcpInstance.FileNameFriendly.IndexOfAny(invalidChars) >= 0);
282+
Assert.IsFalse(withPort.FileNameFriendly.IndexOfAny(invalidChars) >= 0);
283+
Assert.IsFalse(regular.FileNameFriendly.IndexOfAny(invalidChars) >= 0);
284+
}
285+
286+
/// <summary>
287+
/// Tests that FileNameFriendly handles edge cases including IPv6 addresses
288+
/// </summary>
289+
[TestMethod]
290+
public void TestFileNameFriendlyEdgeCases()
291+
{
292+
// Test with IPv6 address (contains colons and brackets)
293+
var ipv6 = new DbaInstanceParameter("::1");
294+
var invalidChars = System.IO.Path.GetInvalidFileNameChars();
295+
Assert.IsFalse(ipv6.FileNameFriendly.IndexOfAny(invalidChars) >= 0,
296+
"IPv6 address FileNameFriendly should not contain invalid filename characters");
297+
298+
// Test with IPv6 address and port
299+
var ipv6Port = new DbaInstanceParameter("[::1]:1433");
300+
Assert.IsFalse(ipv6Port.FileNameFriendly.IndexOfAny(invalidChars) >= 0,
301+
"IPv6 with port FileNameFriendly should not contain invalid filename characters");
302+
303+
// Test with regular IPv6 address
304+
var ipv6Full = new DbaInstanceParameter("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
305+
Assert.IsFalse(ipv6Full.FileNameFriendly.IndexOfAny(invalidChars) >= 0,
306+
"Full IPv6 address FileNameFriendly should not contain invalid filename characters");
307+
308+
// Test with IPv4 address and port (contains colon)
309+
var ipv4Port = new DbaInstanceParameter("192.168.1.1:1433");
310+
Assert.IsFalse(ipv4Port.FileNameFriendly.IndexOfAny(invalidChars) >= 0,
311+
"IPv4 with port FileNameFriendly should not contain invalid filename characters");
312+
313+
// Test that the results are not empty
314+
Assert.IsFalse(string.IsNullOrWhiteSpace(ipv6.FileNameFriendly));
315+
Assert.IsFalse(string.IsNullOrWhiteSpace(ipv6Port.FileNameFriendly));
316+
Assert.IsFalse(string.IsNullOrWhiteSpace(ipv6Full.FileNameFriendly));
317+
Assert.IsFalse(string.IsNullOrWhiteSpace(ipv4Port.FileNameFriendly));
318+
}
241319
}
242320
}

project/dbatools/Parameter/DbaInstanceParameter.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.IO;
3+
using System.Linq;
24
using System.Management.Automation;
35
using System.Net;
46
using System.Net.NetworkInformation;
@@ -118,6 +120,49 @@ public string FullSmoName
118120
}
119121
}
120122

123+
/// <summary>
124+
/// Full name of the instance sanitized for use in file names.
125+
/// Replaces all Windows invalid filename characters (including : \ / | ? * " &lt; &gt;) with underscores.
126+
/// Safe to use as part of a filename or path component.
127+
/// </summary>
128+
[ParameterContract(ParameterContractType.Field, ParameterContractBehavior.Mandatory)]
129+
public string FileNameFriendly
130+
{
131+
get
132+
{
133+
string temp = _ComputerName;
134+
if (_NetworkProtocol == SqlConnectionProtocol.NP) { temp = "NP_" + temp; }
135+
if (_NetworkProtocol == SqlConnectionProtocol.TCP) { temp = "TCP_" + temp; }
136+
137+
string result;
138+
if (!String.IsNullOrEmpty(_InstanceName) && _Port > 0)
139+
{
140+
result = String.Format(@"{0}_{1}_{2}", temp, _InstanceName, _Port);
141+
}
142+
else if (_Port > 0)
143+
{
144+
result = temp + "_" + _Port;
145+
}
146+
else if (!String.IsNullOrEmpty(_InstanceName))
147+
{
148+
result = temp + "_" + _InstanceName;
149+
}
150+
else
151+
{
152+
result = temp;
153+
}
154+
155+
// Sanitize any remaining invalid filename characters
156+
char[] invalidChars = Path.GetInvalidFileNameChars();
157+
foreach (char c in invalidChars)
158+
{
159+
result = result.Replace(c, '_');
160+
}
161+
162+
return result;
163+
}
164+
}
165+
121166
/// <summary>
122167
/// Name of the computer as used in an SQL Statement
123168
/// </summary>

0 commit comments

Comments
 (0)