@@ -60,6 +60,348 @@ $collection = [System.Collections.ArrayList]::new()
6060
6161When in doubt about version compatibility, use the ` New-Object ` cmdlet approach.
6262
63+ ### SQL SERVER VERSION SUPPORT
64+
65+ ** GUIDING PRINCIPLE** : Support SQL Server 2000 when feasible and not overly complex. Balance maintenance burden with real-world user needs.
66+
67+ ** Version Number Mapping:**
68+ - SQL Server 2000 = Version 8 (` $server.VersionMajor -eq 8 ` )
69+ - SQL Server 2005 = Version 9 (` $server.VersionMajor -eq 9 ` )
70+ - SQL Server 2008/2008 R2 = Version 10
71+ - SQL Server 2012 = Version 11
72+ - SQL Server 2014 = Version 12
73+ - SQL Server 2016 = Version 13
74+ - SQL Server 2017 = Version 14
75+ - SQL Server 2019 = Version 15
76+ - SQL Server 2022 = Version 16
77+
78+ ** Philosophy:**
79+ - ** Support SQL Server 2000 when it is not complex or does not add significantly to the codebase**
80+ - ** Skip SQL Server 2000 gracefully when the feature requires SQL 2005+ functionality**
81+ - Never be dismissive or judgmental about users running old SQL Server versions
82+ - Respect that users may be dealing with legacy systems beyond their control
83+ - Balance maintenance and support - practical, not ideological
84+
85+ ** Three Patterns for Version Handling:**
86+
87+ 1 . ** MinimumVersion Parameter** (most common for SQL 2005+ features):
88+ ``` powershell
89+ # Requires SQL Server 2005 or higher
90+ $server = Connect-DbaInstance -SqlInstance $instance -SqlCredential $SqlCredential -MinimumVersion 9
91+
92+ # Results in clear error message:
93+ # "SQL Server version 9 required - server not supported."
94+ ```
95+
96+ 2 . ** Direct Version Checking with throw** (for features unavailable in older versions):
97+ ``` powershell
98+ # When feature is only available in SQL 2005+
99+ if ($sourceServer.VersionMajor -lt 9 -or $destServer.VersionMajor -lt 9) {
100+ throw "Server AlertCategories are only supported in SQL Server 2005 and above. Quitting."
101+ }
102+ ```
103+
104+ 3 . ** Conditional Logic for Backward Compatibility** (when SQL 2000 support is feasible):
105+ ``` powershell
106+ # Different queries or logic for SQL Server 2000
107+ if ($server.VersionMajor -eq 8) {
108+ # SQL Server 2000 uses different system tables
109+ $HeaderInfo = Get-BackupAncientHistory -SqlInstance $server -Database $dbName
110+ } else {
111+ # SQL Server 2005+ uses catalog views
112+ $HeaderInfo = Get-DbaDbBackupHistory -SqlInstance $server -Database $dbName
113+ }
114+
115+ # SQL Server 2000 may need different default paths
116+ if ($null -eq $PSBoundParameters.Path -and $server.VersionMajor -eq 8) {
117+ $Path = (Get-DbaDefaultPath -SqlInstance $server).Backup
118+ }
119+ ```
120+
121+ ** Common Reasons to Require SQL Server 2005+:**
122+
123+ SQL Server 2005 introduced many foundational changes that make backward compatibility difficult:
124+ - Catalog views (` sys.* ` ) replaced system tables (` sysobjects ` , ` syscomments ` , etc.)
125+ - ` SCHEMA_NAME() ` and schema-based security
126+ - New object types and features (e.g., Service Broker, CLR integration)
127+ - DMVs (Dynamic Management Views)
128+ - Deprecated features like Extended Stored Procedures (deprecated in 2005, favor CLR)
129+
130+ ** When to Use Each Pattern:**
131+
132+ - ** Use MinimumVersion 9** when the feature fundamentally requires SQL 2005+ (catalog views, schemas, DMVs)
133+ - ** Use explicit version checking** when you need a clearer error message or version-specific logic paths
134+ - ** Use conditional logic** when SQL 2000 support is straightforward (different system tables, minor syntax differences)
135+
136+ ** Documentation Standards:**
137+
138+ When a command requires a specific SQL Server version, document it in the help:
139+
140+ ``` powershell
141+ .PARAMETER SqlInstance
142+ The target SQL Server instance or instances. Must be SQL Server 2005 or higher.
143+
144+ .PARAMETER Source
145+ Source SQL Server instance. You must have sysadmin access and server version must be SQL Server 2000 or higher.
146+ ```
147+
148+ ** Examples from the Codebase:**
149+
150+ Commands that support SQL Server 2000:
151+ - ` Copy-DbaAgentAlert ` , ` Copy-DbaAgentJob ` , ` Copy-DbaAgentOperator ` , ` Copy-DbaAgentProxy ` , ` Copy-DbaAgentServer `
152+ - ` Copy-DbaBackupDevice ` , ` Copy-DbaCustomError ` , ` Copy-DbaLogin `
153+ - ` Backup-DbaDatabase ` (with version-specific handling)
154+ - ` Copy-DbaDatabase ` (with restrictions: cannot migrate SQL 2000 to SQL 2012+)
155+
156+ Commands that require SQL Server 2005+:
157+ - ` Copy-DbaAgentJobCategory ` (uses AlertCategories only available in SQL 2005+)
158+ - ` Copy-DbaAgentProxy ` (uses MinimumVersion 9)
159+ - Most commands using catalog views, DMVs, or SQL 2005+ features
160+
161+ ** Important** : Never be dismissive or judgmental about users running old SQL Server versions. Provide respectful, factual, technical explanations.
162+
163+ ### SMO vs T-SQL USAGE
164+
165+ ** GUIDING PRINCIPLE** : Default to using SMO (SQL Server Management Objects) first. Only use T-SQL when SMO doesn't provide the functionality or when T-SQL offers better performance or user experience.
166+
167+ ** Why SMO First:**
168+ - ** Abstraction** : SMO provides object-oriented interface that handles version differences automatically
169+ - ** Type Safety** : Strong typing reduces errors compared to dynamic T-SQL strings
170+ - ** Built-in Methods** : Common operations (Create, Drop, Alter, Script) are provided out-of-the-box
171+ - ** Consistency** : SMO ensures consistent behavior across SQL Server versions
172+ - ** Less Code** : Often requires fewer lines than equivalent T-SQL
173+
174+ ** When to Use SMO:**
175+
176+ 1 . ** Object Manipulation** - Creating, dropping, altering database objects:
177+ ``` powershell
178+ # PREFERRED - SMO for object manipulation
179+ $newdb = New-Object Microsoft.SqlServer.Management.Smo.Database($server, $dbName)
180+ $newdb.Collation = $Collation
181+ $newdb.RecoveryModel = $RecoveryModel
182+ $newdb.Create()
183+
184+ # Dropping objects
185+ $destServer.Roles[$roleName].Drop()
186+ $destServer.Roles.Refresh()
187+ ```
188+
189+ 2 . ** Object Scripting** - Generating T-SQL from existing objects:
190+ ``` powershell
191+ # PREFERRED - SMO scripting with execution via Query
192+ $sql = $currentRole.Script() | Out-String
193+ Write-Message -Level Debug -Message $sql
194+ $destServer.Query($sql)
195+
196+ # Another example
197+ $destServer.Query($currentEndpoint.Script()) | Out-Null
198+ ```
199+
200+ 3 . ** Object Enumeration** - Accessing collections and properties:
201+ ``` powershell
202+ # PREFERRED - SMO for object access
203+ $databases = $server.Databases
204+ $database = $server.Databases[$dbName]
205+ $isSystemDb = $database.IsSystemObject
206+ $members = $currentRole.EnumMemberNames()
207+ ```
208+
209+ 4 . ** Object Properties** - Reading and setting object attributes:
210+ ``` powershell
211+ # PREFERRED - SMO for property access
212+ $recoveryModel = $db.RecoveryModel
213+ $owner = $db.Owner
214+ $lastBackup = $db.LastBackupDate
215+ $size = $db.Size
216+ ```
217+
218+ ** When T-SQL is Appropriate:**
219+
220+ 1 . ** System Views and DMVs** - When SMO doesn't expose the data efficiently:
221+ ``` powershell
222+ # T-SQL for system catalog queries
223+ $sql = @"
224+ SELECT
225+ p.name AS ProcedureName,
226+ SCHEMA_NAME(p.schema_id) AS SchemaName,
227+ p.object_id,
228+ m.definition AS DllPath
229+ FROM sys.procedures p
230+ INNER JOIN sys.all_objects o ON p.object_id = o.object_id
231+ LEFT JOIN sys.sql_modules m ON p.object_id = m.object_id
232+ WHERE p.type = 'X'
233+ AND p.is_ms_shipped = 0
234+ ORDER BY p.name
235+ "@
236+ $sourceXPs = $sourceServer.Query($sql)
237+ ```
238+
239+ 2 . ** Performance-Critical Queries** - When retrieving large result sets:
240+ ``` powershell
241+ # T-SQL for efficient data retrieval
242+ $querylastused = "SELECT dbname, max(last_read) last_read FROM sys.dm_db_index_usage_stats GROUP BY dbname"
243+ $dblastused = $server.Query($querylastused)
244+ ```
245+
246+ 3 . ** Version-Specific Logic** - Different queries for different SQL Server versions:
247+ ``` powershell
248+ # T-SQL when version-specific system tables/views are needed
249+ if ($server.VersionMajor -eq 8) {
250+ # SQL Server 2000 uses system tables
251+ $backed_info = $server.Query("SELECT name, SUSER_SNAME(sid) AS [Owner] FROM master.dbo.sysdatabases")
252+ } else {
253+ # SQL Server 2005+ uses catalog views
254+ $backed_info = $server.Query("SELECT name, SUSER_SNAME(owner_sid) AS [Owner] FROM sys.databases")
255+ }
256+ ```
257+
258+ 4 . ** System Stored Procedures** - When the operation requires a specific system proc:
259+ ``` powershell
260+ # T-SQL for system procedures that have no SMO equivalent
261+ $dropSql = "EXEC sp_dropextendedproc @functname = N'$xpFullName'"
262+ $null = $destServer.Query($dropSql)
263+
264+ $createSql = "EXEC sp_addextendedproc @functname = N'$xpFullName', @dllname = N'$destDllPath'"
265+ $null = $destServer.Query($createSql)
266+ ```
267+
268+ 5 . ** User-Friendly Features** - When T-SQL makes the command more intuitive:
269+ ``` powershell
270+ # Sometimes T-SQL provides better UX than SMO
271+ # Example: Parameterized queries for filtering
272+ $splatQuery = @{
273+ SqlInstance = $instance
274+ Query = "SELECT * FROM users WHERE Givenname = @name"
275+ SqlParameter = @{ Name = "Maria" }
276+ }
277+ $result = Invoke-DbaQuery @splatQuery
278+ ```
279+
280+ ** Hybrid Pattern (Most Common):**
281+
282+ Most dbatools commands use both SMO and T-SQL strategically:
283+
284+ ``` powershell
285+ # Get SMO server object
286+ $sourceServer = Connect-DbaInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
287+
288+ # Use SMO for object enumeration
289+ $sourceRoles = $sourceServer.Roles | Where-Object IsFixedRole -eq $false
290+
291+ # Use T-SQL for complex permission queries
292+ $splatPermissions = @{
293+ SqlInstance = $sourceServer
294+ IncludeServerLevel = $true
295+ }
296+ $sourcePermissions = Get-DbaPermission @splatPermissions | Where-Object Grantee -eq $roleName
297+
298+ # Use SMO for object manipulation
299+ foreach ($currentRole in $sourceRoles) {
300+ # Script the object using SMO
301+ $sql = $currentRole.Script() | Out-String
302+
303+ # Execute via T-SQL
304+ $destServer.Query($sql)
305+
306+ # Use SMO methods for membership
307+ $members = $currentRole.EnumMemberNames()
308+ foreach ($member in $members) {
309+ $destServer.Roles[$roleName].AddMember($member)
310+ }
311+ }
312+ ```
313+
314+ ** Decision Tree:**
315+
316+ 1 . ** Does SMO expose the functionality cleanly?**
317+ - YES → Use SMO
318+ - NO → Continue to #2
319+
320+ 2 . ** Is this a data retrieval operation from system views/DMVs?**
321+ - YES → Use T-SQL via ` $server.Query() `
322+ - NO → Continue to #3
323+
324+ 3 . ** Does the operation require a system stored procedure?**
325+ - YES → Use T-SQL via ` $server.Query() `
326+ - NO → Continue to #4
327+
328+ 4 . ** Would T-SQL significantly improve user experience?**
329+ - YES → Use T-SQL (document why in comments)
330+ - NO → Use SMO
331+
332+ ** Common Patterns:**
333+
334+ ``` powershell
335+ # Pattern 1: SMO object with T-SQL execution of Script()
336+ $sql = $smoObject.Script() | Out-String
337+ $destServer.Query($sql)
338+
339+ # Pattern 2: T-SQL for discovery, SMO for manipulation
340+ $objects = $server.Query("SELECT name FROM sys.objects WHERE type = 'U'")
341+ foreach ($obj in $objects) {
342+ $table = $server.Databases[$dbName].Tables[$obj.name]
343+ $table.Drop() # SMO method
344+ }
345+
346+ # Pattern 3: SMO with T-SQL fallback
347+ try {
348+ $database = $server.Databases[$dbName] # SMO
349+ } catch {
350+ # Fallback to T-SQL if SMO fails
351+ $result = $server.Query("SELECT name FROM sys.databases WHERE name = '$dbName'")
352+ }
353+ ```
354+
355+ ** Copy-DbaExtendedStoredProcedure Analysis:**
356+
357+ The newly created ` Copy-DbaExtendedStoredProcedure ` command demonstrates proper SMO vs T-SQL usage:
358+
359+ - ✅ ** Correct** : Uses T-SQL for querying ` sys.procedures ` (lines 122-134) - SMO doesn't expose Extended SP metadata efficiently
360+ - ✅ ** Correct** : Uses T-SQL system procedures ` sp_dropextendedproc ` and ` sp_addextendedproc ` (lines 235, 307) - No SMO equivalent
361+ - ✅ ** Correct** : Uses SMO properties ` $sourceServer.RootDirectory ` (line 181) - Cleaner than querying registry
362+ - ✅ ** Correct** : Uses T-SQL ` sp_helpextendedproc ` (line 254) - System procedure for metadata
363+
364+ This is a good example of the hybrid pattern where T-SQL is used appropriately because:
365+ 1 . Extended Stored Procedures are a legacy feature with limited SMO support
366+ 2 . System stored procedures are the documented way to manage them
367+ 3 . System catalog views provide the metadata SMO doesn't expose
368+
369+ ** Anti-Patterns to Avoid:**
370+
371+ ``` powershell
372+ # WRONG - Using T-SQL when SMO provides the functionality
373+ $result = $server.Query("ALTER DATABASE [$dbName] SET RECOVERY FULL")
374+
375+ # CORRECT - Use SMO
376+ $db = $server.Databases[$dbName]
377+ $db.RecoveryModel = "Full"
378+ $db.Alter()
379+
380+ # WRONG - Using T-SQL for object enumeration
381+ $databases = $server.Query("SELECT name FROM sys.databases")
382+
383+ # CORRECT - Use SMO
384+ $databases = $server.Databases
385+
386+ # WRONG - Concatenating T-SQL strings without parameters (SQL injection risk)
387+ $result = $server.Query("SELECT * FROM users WHERE name = '$userName'")
388+
389+ # CORRECT - Use parameterized queries
390+ $splatQuery = @{
391+ Query = "SELECT * FROM users WHERE name = @userName"
392+ SqlParameter = @{ userName = $userName }
393+ }
394+ $result = Invoke-DbaQuery @splatQuery -SqlInstance $server
395+ ```
396+
397+ ** Summary:**
398+
399+ - ** Default to SMO** for object-oriented operations (Create, Drop, Alter, Script, property access)
400+ - ** Use T-SQL** for system views, DMVs, complex queries, system stored procedures, and version-specific logic
401+ - ** Combine both** in a hybrid approach when it provides the best balance of functionality and usability
402+ - ** Always prefer parameterized queries** when using T-SQL with dynamic values
403+ - ** Document your choice** when T-SQL is used instead of SMO for non-obvious reasons
404+
63405### SPLAT USAGE REQUIREMENT
64406
65407** USE SPLATS ONLY FOR 3+ PARAMETERS**
@@ -634,6 +976,8 @@ Don't add excessive tests, but don't skip tests either. When making changes:
634976- [ ] No ` ::new() ` syntax used (PowerShell v3+ compatible)
635977- [ ] No v5+ language features used
636978- [ ] ` New-Object ` used for object instantiation
979+ - [ ] SQL Server version support follows project philosophy (support 2000 when feasible)
980+ - [ ] Version requirements documented in parameter help if applicable
637981
638982** Style Requirements:**
639983- [ ] Double quotes used for all strings
@@ -644,6 +988,8 @@ Don't add excessive tests, but don't skip tests either. When making changes:
644988- [ ] No trailing spaces anywhere
645989
646990** dbatools Patterns:**
991+ - [ ] SMO used first for object manipulation, scripting, and property access
992+ - [ ] T-SQL only used when appropriate (system views, DMVs, stored procedures, performance, version-specific)
647993- [ ] EnableException handling correctly implemented
648994- [ ] Parameter validation follows dbatools pattern
649995- [ ] Where-Object conversions applied appropriately
@@ -674,9 +1020,11 @@ The golden rules for dbatools code:
67410201 . ** NEVER use backticks** - Use splats for 3+ parameters, direct syntax for 1-2
67510212 . ** NEVER use ` = $true ` in parameter attributes** - Use modern syntax: ` [Parameter(Mandatory)] ` not ` [Parameter(Mandatory = $true)] `
67610223 . ** NEVER use ` ::new() ` syntax** - Use ` New-Object ` for PowerShell v3 compatibility
677- 4 . ** ALWAYS align hashtables** - Equals signs must line up vertically
678- 5 . ** ALWAYS preserve comments** - Every comment stays exactly as written
679- 6 . ** ALWAYS use double quotes** - SQL Server module standard
680- 7 . ** ALWAYS use unique variable names** - Prevent scope collisions
681- 8 . ** ALWAYS use descriptive splatnames** - ` $splatConnection ` , not ` $splat `
682- 9 . ** ALWAYS register new commands** - Add to both dbatools.psd1 and dbatools.psm1
1023+ 4 . ** NEVER be dismissive about SQL Server versions** - Support SQL 2000 when feasible, skip gracefully when not
1024+ 5 . ** ALWAYS prefer SMO first** - Use T-SQL only when SMO doesn't provide functionality or for better performance/UX
1025+ 6 . ** ALWAYS align hashtables** - Equals signs must line up vertically
1026+ 7 . ** ALWAYS preserve comments** - Every comment stays exactly as written
1027+ 8 . ** ALWAYS use double quotes** - SQL Server module standard
1028+ 9 . ** ALWAYS use unique variable names** - Prevent scope collisions
1029+ 10 . ** ALWAYS use descriptive splatnames** - ` $splatConnection ` , not ` $splat `
1030+ 11 . ** ALWAYS register new commands** - Add to both dbatools.psd1 and dbatools.psm1
0 commit comments