Skip to content

Commit 0667d98

Browse files
feat(migration): Convert Get-DbaDeprecatedFeature to C# binary cmdlet
- All parameters preserved (SqlInstance, SqlCredential, EnableException) - All code paths implemented (connection, query, output) - Build passes on net472 and net8.0 - C# unit tests written and passing (10 tests) - Pester integration tests pass (4/4, improved from 1/2 baseline) - Feature parity verified at 100% - PS1 retired, cmdlet exported from dbatools.library Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 19ad8a8 commit 0667d98

4 files changed

Lines changed: 318 additions & 1 deletion

File tree

dbatools.library.psd1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
'Get-DbaCmObject',
8282
'Get-DbaCpuRingBuffer',
8383
'Get-DbaCpuUsage',
84+
'Get-DbaDeprecatedFeature',
8485
'Get-DbaConnectedInstance',
8586
'Get-DbaConnection',
8687
'Get-DbatoolsChangeLog',

docs/plan/TRACKER-MIGRATE-INSTANCE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
| 1 | Get-DbaBuild | DONE | GetDbaBuildCommand.cs | OK | 100% | 26/26 | Read-only, no deps |
1111
| 2 | Get-DbaCpuRingBuffer | DONE | GetDbaCpuRingBufferCommand.cs | OK | 100% | 2/2 | Read-only, no deps |
1212
| 3 | Get-DbaCpuUsage | DONE | GetDbaCpuUsageCommand.cs | OK | 100% | 2/2 | Read-only, no deps |
13-
| 4 | Get-DbaDeprecatedFeature | PENDING | | | | | Read-only, no deps |
13+
| 4 | Get-DbaDeprecatedFeature | DONE | GetDbaDeprecatedFeatureCommand.cs | OK | 100% | 4/4 | Read-only, no deps |
1414
| 5 | Get-DbaDump | PENDING | | | | | Read-only, no deps |
1515
| 6 | Get-DbaErrorLog | PENDING | | | | | Read-only, no deps |
1616
| 7 | Get-DbaErrorLogConfig | PENDING | | | | | Read-only, no deps |
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
using System;
2+
using System.Data;
3+
using System.Management.Automation;
4+
using Microsoft.VisualStudio.TestTools.UnitTesting;
5+
using Dataplat.Dbatools.Commands;
6+
7+
namespace Dataplat.Dbatools.Tests.Commands
8+
{
9+
[TestClass]
10+
public class GetDbaDeprecatedFeatureCommandTests
11+
{
12+
#region GetServerProperty
13+
[TestMethod]
14+
public void GetServerProperty_NullServer_ReturnsEmpty()
15+
{
16+
string result = GetDbaDeprecatedFeatureCommand.GetServerProperty(null, "ComputerName");
17+
Assert.AreEqual(String.Empty, result);
18+
}
19+
20+
[TestMethod]
21+
public void GetServerProperty_ValidProperty_ReturnsValue()
22+
{
23+
PSObject server = new PSObject();
24+
server.Properties.Add(new PSNoteProperty("ComputerName", "SQLSERVER01"));
25+
string result = GetDbaDeprecatedFeatureCommand.GetServerProperty(server, "ComputerName");
26+
Assert.AreEqual("SQLSERVER01", result);
27+
}
28+
29+
[TestMethod]
30+
public void GetServerProperty_MissingProperty_ReturnsEmpty()
31+
{
32+
PSObject server = new PSObject();
33+
server.Properties.Add(new PSNoteProperty("ComputerName", "SQLSERVER01"));
34+
string result = GetDbaDeprecatedFeatureCommand.GetServerProperty(server, "NonExistent");
35+
Assert.AreEqual(String.Empty, result);
36+
}
37+
38+
[TestMethod]
39+
public void GetServerProperty_NullPropertyValue_ReturnsEmpty()
40+
{
41+
PSObject server = new PSObject();
42+
server.Properties.Add(new PSNoteProperty("ComputerName", null));
43+
string result = GetDbaDeprecatedFeatureCommand.GetServerProperty(server, "ComputerName");
44+
Assert.AreEqual(String.Empty, result);
45+
}
46+
#endregion GetServerProperty
47+
48+
#region GetRowValue
49+
[TestMethod]
50+
public void GetRowValue_NullRow_ReturnsNull()
51+
{
52+
object result = GetDbaDeprecatedFeatureCommand.GetRowValue(null, "DeprecatedFeature");
53+
Assert.IsNull(result);
54+
}
55+
56+
[TestMethod]
57+
public void GetRowValue_PSObjectWithProperty_ReturnsValue()
58+
{
59+
PSObject row = new PSObject();
60+
row.Properties.Add(new PSNoteProperty("DeprecatedFeature", "sysdatabases"));
61+
object result = GetDbaDeprecatedFeatureCommand.GetRowValue(row, "DeprecatedFeature");
62+
Assert.AreEqual("sysdatabases", result);
63+
}
64+
65+
[TestMethod]
66+
public void GetRowValue_PSObjectMissingProperty_ReturnsNull()
67+
{
68+
PSObject row = new PSObject();
69+
row.Properties.Add(new PSNoteProperty("DeprecatedFeature", "sysdatabases"));
70+
object result = GetDbaDeprecatedFeatureCommand.GetRowValue(row, "NonExistent");
71+
Assert.IsNull(result);
72+
}
73+
74+
[TestMethod]
75+
public void GetRowValue_DataRow_ReturnsValue()
76+
{
77+
DataTable table = new DataTable();
78+
table.Columns.Add("DeprecatedFeature", typeof(string));
79+
table.Columns.Add("UsageCount", typeof(long));
80+
DataRow dataRow = table.NewRow();
81+
dataRow["DeprecatedFeature"] = "sysdatabases";
82+
dataRow["UsageCount"] = 42L;
83+
table.Rows.Add(dataRow);
84+
85+
PSObject row = PSObject.AsPSObject(dataRow);
86+
object featureResult = GetDbaDeprecatedFeatureCommand.GetRowValue(row, "DeprecatedFeature");
87+
Assert.AreEqual("sysdatabases", featureResult);
88+
89+
object countResult = GetDbaDeprecatedFeatureCommand.GetRowValue(row, "UsageCount");
90+
Assert.AreEqual(42L, countResult);
91+
}
92+
93+
[TestMethod]
94+
public void GetRowValue_DataRowWithDBNull_ReturnsNull()
95+
{
96+
DataTable table = new DataTable();
97+
table.Columns.Add("DeprecatedFeature", typeof(string));
98+
DataRow dataRow = table.NewRow();
99+
dataRow["DeprecatedFeature"] = DBNull.Value;
100+
table.Rows.Add(dataRow);
101+
102+
PSObject row = PSObject.AsPSObject(dataRow);
103+
object result = GetDbaDeprecatedFeatureCommand.GetRowValue(row, "DeprecatedFeature");
104+
Assert.IsNull(result);
105+
}
106+
#endregion GetRowValue
107+
108+
#region DeprecatedFeatureQuery
109+
[TestMethod]
110+
public void DeprecatedFeatureQuery_ContainsExpectedSqlElements()
111+
{
112+
string query = GetDbaDeprecatedFeatureCommand.DeprecatedFeatureQuery;
113+
Assert.IsTrue(query.Contains("sys.dm_os_performance_counters"), "Query should reference sys.dm_os_performance_counters");
114+
Assert.IsTrue(query.Contains("Deprecated Features"), "Query should filter for Deprecated Features");
115+
Assert.IsTrue(query.Contains("cntr_value > 0"), "Query should filter for non-zero usage counts");
116+
Assert.IsTrue(query.Contains("LTRIM(RTRIM(instance_name))"), "Query should trim instance_name");
117+
Assert.IsTrue(query.Contains("AS DeprecatedFeature"), "Query should alias as DeprecatedFeature");
118+
Assert.IsTrue(query.Contains("AS UsageCount"), "Query should alias as UsageCount");
119+
}
120+
#endregion DeprecatedFeatureQuery
121+
}
122+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
using System;
2+
using System.Collections.ObjectModel;
3+
using System.Data;
4+
using System.Management.Automation;
5+
using Dataplat.Dbatools.Parameter;
6+
7+
namespace Dataplat.Dbatools.Commands
8+
{
9+
/// <summary>
10+
/// Identifies deprecated SQL Server features currently in use with their usage counts from performance counters.
11+
/// Queries sys.dm_os_performance_counters to find which deprecated features have been used on the instance.
12+
/// </summary>
13+
[Cmdlet("Get", "DbaDeprecatedFeature")]
14+
public class GetDbaDeprecatedFeatureCommand : DbaInstanceCmdlet
15+
{
16+
private static readonly ScriptBlock ConnectScript =
17+
ScriptBlock.Create("param($i) Connect-DbaInstance -SqlInstance $i -MinimumVersion 9");
18+
private static readonly ScriptBlock ConnectWithCredScript =
19+
ScriptBlock.Create("param($i, $c) Connect-DbaInstance -SqlInstance $i -SqlCredential $c -MinimumVersion 9");
20+
private static readonly ScriptBlock QueryScript =
21+
ScriptBlock.Create("param($s, $q) $s.Query($q)");
22+
23+
/// <summary>
24+
/// The SQL query to retrieve deprecated features with usage counts greater than zero.
25+
/// </summary>
26+
internal static readonly string DeprecatedFeatureQuery =
27+
"SELECT LTRIM(RTRIM(instance_name)) AS DeprecatedFeature, cntr_value AS UsageCount FROM sys.dm_os_performance_counters WHERE object_name LIKE '%SQL%Deprecated Features%' AND cntr_value > 0";
28+
29+
/// <summary>
30+
/// Processes each SQL Server instance, querying for deprecated feature usage.
31+
/// </summary>
32+
protected override void ProcessRecord()
33+
{
34+
if (TestFunctionInterrupt()) { return; }
35+
36+
foreach (DbaInstanceParameter instance in SqlInstance)
37+
{
38+
object server;
39+
try
40+
{
41+
server = ConnectInstance(instance);
42+
if (server == null)
43+
{
44+
StopFunction(
45+
String.Format("Failed to connect to {0}", instance),
46+
target: instance,
47+
isContinue: true,
48+
category: ErrorCategory.ConnectionError);
49+
TestFunctionInterrupt();
50+
continue;
51+
}
52+
}
53+
catch (Exception ex)
54+
{
55+
StopFunction(
56+
"Failure",
57+
errorRecord: new ErrorRecord(ex, "GetDbaDeprecatedFeature_ConnectionError", ErrorCategory.ConnectionError, instance),
58+
target: instance,
59+
isContinue: true,
60+
category: ErrorCategory.ConnectionError);
61+
TestFunctionInterrupt();
62+
continue;
63+
}
64+
65+
try
66+
{
67+
string computerName = GetServerProperty(server, "ComputerName");
68+
string serviceName = GetServerProperty(server, "ServiceName");
69+
string domainInstanceName = GetServerProperty(server, "DomainInstanceName");
70+
71+
Collection<PSObject> rows = ExecuteQuery(server, DeprecatedFeatureQuery);
72+
if (rows != null)
73+
{
74+
foreach (PSObject row in rows)
75+
{
76+
if (row == null) continue;
77+
78+
PSObject output = new PSObject();
79+
output.Properties.Add(new PSNoteProperty("ComputerName", computerName));
80+
output.Properties.Add(new PSNoteProperty("InstanceName", serviceName));
81+
output.Properties.Add(new PSNoteProperty("SqlInstance", domainInstanceName));
82+
output.Properties.Add(new PSNoteProperty("DeprecatedFeature", GetRowValue(row, "DeprecatedFeature")));
83+
output.Properties.Add(new PSNoteProperty("UsageCount", GetRowValue(row, "UsageCount")));
84+
85+
WriteObject(output);
86+
}
87+
}
88+
}
89+
catch (Exception ex)
90+
{
91+
StopFunction(
92+
String.Format("Failed to retrieve deprecated features from {0}", instance),
93+
exception: ex,
94+
target: instance,
95+
isContinue: true);
96+
TestFunctionInterrupt();
97+
continue;
98+
}
99+
}
100+
}
101+
102+
#region Helpers
103+
104+
/// <summary>
105+
/// Connects to a SQL Server instance via Connect-DbaInstance with minimum version 9.
106+
/// </summary>
107+
private object ConnectInstance(DbaInstanceParameter instance)
108+
{
109+
Collection<PSObject> results;
110+
if (SqlCredential != null)
111+
{
112+
results = InvokeCommand.InvokeScript(false, ConnectWithCredScript, null, new object[] { instance, SqlCredential });
113+
}
114+
else
115+
{
116+
results = InvokeCommand.InvokeScript(false, ConnectScript, null, new object[] { instance });
117+
}
118+
119+
if (results != null && results.Count > 0)
120+
return results[0].BaseObject;
121+
return null;
122+
}
123+
124+
/// <summary>
125+
/// Executes a SQL query against the server object using $server.Query().
126+
/// </summary>
127+
private Collection<PSObject> ExecuteQuery(object server, string sql)
128+
{
129+
return InvokeCommand.InvokeScript(false, QueryScript, null, new object[] { server, sql });
130+
}
131+
132+
/// <summary>
133+
/// Gets a property value from a server object using PSObject property access.
134+
/// </summary>
135+
internal static string GetServerProperty(object server, string propertyName)
136+
{
137+
if (server == null) return String.Empty;
138+
try
139+
{
140+
PSObject pso = PSObject.AsPSObject(server);
141+
PSPropertyInfo prop = pso.Properties[propertyName];
142+
if (prop != null && prop.Value != null)
143+
return prop.Value.ToString();
144+
}
145+
catch (Exception)
146+
{
147+
// Property may not exist on this object type
148+
}
149+
return String.Empty;
150+
}
151+
152+
/// <summary>
153+
/// Gets a value from a query result row. Handles both DataRow and PSObject.
154+
/// </summary>
155+
internal static object GetRowValue(PSObject row, string columnName)
156+
{
157+
if (row == null) return null;
158+
159+
object baseObj = row.BaseObject;
160+
if (baseObj is DataRow dataRow)
161+
{
162+
try
163+
{
164+
object val = dataRow[columnName];
165+
if (val == DBNull.Value) return null;
166+
return val;
167+
}
168+
catch (Exception)
169+
{
170+
return null;
171+
}
172+
}
173+
174+
// Fallback: try PSObject property
175+
try
176+
{
177+
PSPropertyInfo prop = row.Properties[columnName];
178+
if (prop != null)
179+
{
180+
object val = prop.Value;
181+
if (val == DBNull.Value) return null;
182+
return val;
183+
}
184+
}
185+
catch (Exception)
186+
{
187+
// Ignore
188+
}
189+
return null;
190+
}
191+
192+
#endregion Helpers
193+
}
194+
}

0 commit comments

Comments
 (0)