Skip to content

Commit 9cf09cf

Browse files
Copy-DbaExtendedStoredProcedure - Add command to copy custom Extended Stored Procedures (#9960)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
1 parent 0739572 commit 9cf09cf

File tree

6 files changed

+773
-7
lines changed

6 files changed

+773
-7
lines changed

CLAUDE.md

Lines changed: 354 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,348 @@ $collection = [System.Collections.ArrayList]::new()
6060

6161
When 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:
6741020
1. **NEVER use backticks** - Use splats for 3+ parameters, direct syntax for 1-2
6751021
2. **NEVER use `= $true` in parameter attributes** - Use modern syntax: `[Parameter(Mandatory)]` not `[Parameter(Mandatory = $true)]`
6761022
3. **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

dbatools.psd1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
'Copy-DbaDbTableData',
107107
'Copy-DbaDbViewData',
108108
'Copy-DbaEndpoint',
109+
'Copy-DbaExtendedStoredProcedure',
109110
'Copy-DbaInstanceAudit',
110111
'Copy-DbaInstanceAuditSpecification',
111112
'Copy-DbaInstanceTrigger',

0 commit comments

Comments
 (0)