Skip to content

Commit 4d5b237

Browse files
committed
SqlAgentAlert: Refactor to class-bases resource
1 parent 6488425 commit 4d5b237

13 files changed

+719
-417
lines changed

.github/instructions/SqlServerDsc-guidelines.instructions.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ applyTo: "**"
3434
- Choose the appropriate group number based on the required dependencies
3535

3636
## Unit tests
37-
- When unit test uses SMO types, ensure they are properly stubbed in SMO.cs
38-
- Load stub types from SMO.cs in unit test files, e.g. `Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs"`
39-
- After changing SMO stub types, run tests in a new PowerShell session for changes to take effect.
37+
- When unit test tests classes or commands that contain SMO types, e.g. `[Microsoft.SqlServer.Management.Smo.*]`
38+
- Ensure they are properly stubbed in SMO.cs
39+
- Load SMO stub types from SMO.cs in unit test files, e.g. `Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs"`
40+
- After changing SMO stub types, run tests in a new PowerShell session for changes to take effect.

.github/instructions/dsc-community-style-guidelines-class-resource.instructions.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ applyTo: "source/[cC]lasses/**/*.ps1"
1212
- Decoration: `[DscResource(RunAsCredential = 'Optional')]` (replace with `'Mandatory'` if required)
1313
- Inheritance: Must inherit `ResourceBase` (part of module DscResource.Base)
1414
- `$this.localizedData` hashtable auto-populated by `ResourceBase` from localization file
15+
- Properties must have nullable types (`[Nullable[{Type}]]` where needed)
16+
17+
## Required constructor
18+
19+
```powershell
20+
MyResourceName () : base ()
21+
{
22+
# Property names where state cannot be enforced, e.g. IsSingelInstance
23+
$this.ExcludeDscProperties = @()
24+
}
25+
```
1526

1627
## Required constructor
1728

@@ -78,3 +89,19 @@ hidden [void] NormalizeProperties([System.Collections.Hashtable] $properties)
7889
# Normalize user-provided properties, $properties contains user assigned values
7990
}
8091
```
92+
93+
## Required comment-based help
94+
95+
Add to .DESCRIPTION section:
96+
- `## Requirements`: List minimum requirements
97+
- `## Known issues`: Critical issues + pattern: `All issues are not listed here, see [all open issues](https://github.com/{owner}/{repo}/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+{ResourceName}).`
98+
99+
## Error Handling
100+
- Use `try/catch` blocks to handle exceptions
101+
- Do not use `throw` for terminating errors, use `New-*Exception` commands:
102+
- [`New‑InvalidDataException`](https://github.com/dsccommunity/DscResource.Common/wiki/New%E2%80%91InvalidDataException)
103+
- [`New-ArgumentException`](https://github.com/dsccommunity/DscResource.Common/wiki/New%E2%80%91ArgumentException)
104+
- [`New-InvalidOperationException`](https://github.com/dsccommunity/DscResource.Common/wiki/New%E2%80%91InvalidOperationException)
105+
- [`New-ObjectNotFoundException`](https://github.com/dsccommunity/DscResource.Common/wiki/New%E2%80%91ObjectNotFoundException)
106+
- [`New-InvalidResultException`](https://github.com/dsccommunity/DscResource.Common/wiki/New%E2%80%91InvalidResultException)
107+
- [`New-NotImplementedException`](https://github.com/dsccommunity/DscResource.Common/wiki/New%E2%80%91NotImplementedException)

.github/instructions/dsc-community-style-guidelines-pester.instructions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ applyTo: "**/*.[Tt]ests.ps1"
2929
- `It` descriptions start with 'Should', must not contain 'when'
3030
- Mock variables prefix: 'mock'
3131
- Prefer `-BeTrue`/`-BeFalse` over `-Be $true`/`-Be $false`
32+
- Never use `Assert-MockCalled`, use `Should -Invoke` instead
3233
- No `Should -Not -Throw` - invoke commands directly
3334
- Never add an empty `-MockWith` block
3435
- Omit `-MockWith` when returning `$null`
3536
- Set `$PSDefaultParameterValues` for `Mock:ModuleName`, `Should:ModuleName`, `InModuleScope:ModuleName`
3637
- Omit `-ModuleName` parameter on Pester commands
38+
- Never use `Mock` inside `InModuleScope`-block
3739

3840
## File Organization
3941
- Class resources: `tests/Unit/Classes/{Name}.Tests.ps1`

.github/instructions/dsc-community-style-guidelines-powershell.instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ applyTo: "**/*.ps?(m|d)1"
7979
- For state-changing functions, use `SupportsShouldProcess`
8080
- Place ShouldProcess check immediately before each state-change
8181
- `$PSCmdlet.ShouldProcess` must use required pattern
82-
- Use `$PSCmdlet.ThrowTerminatingError()` for terminating errors, use relevant error category
82+
- Use `$PSCmdlet.ThrowTerminatingError()` for terminating errors (except for classes), use relevant error category
8383
- Use `Write-Error` for non-terminating errors, use relevant error category
8484
- Use `Write-Warning` for warnings
8585
- Use `Write-Debug` for debugging information

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
- Added setup workflow for GitHub Copilot.
1919
- Switch the workflow to use Linux.
2020
- Attempt to unshallow the Copilot branch
21+
- `SqlAgentAlert`
22+
- Added new DSC resource to manage SQL Server Agent alerts.
2123
- Improved AI instructions.
2224
- Enhanced workflow with proper environment variable configuration and DSCv3 verification.
2325
- Fixed environment variable persistence by using $GITHUB_ENV instead of
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
<#
2+
.SYNOPSIS
3+
The `SqlAgentAlert` DSC resource is used to create, modify, or remove
4+
_SQL Server Agent_ alerts.
5+
6+
.DESCRIPTION
7+
The `SqlAgentAlert` DSC resource is used to create, modify, or remove
8+
_SQL Server Agent_ alerts.
9+
10+
The built-in parameter **PSDscRunAsCredential** can be used to run the resource
11+
as another user. The resource will then authenticate to the _SQL Server_
12+
instance as that user. It also possible to instead use impersonation by the
13+
parameter **Credential**.
14+
15+
## Requirements
16+
17+
* Target machine must be running Windows Server 2012 or later.
18+
* Target machine must be running SQL Server Database Engine 2012 or later.
19+
* Target machine must have access to the SQLPS PowerShell module or the SqlServer
20+
PowerShell module.
21+
22+
## Known issues
23+
24+
All issues are not listed here, see [here for all open issues](https://github.com/dsccommunity/SqlServerDsc/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+SqlAgentAlert).
25+
26+
### Property **Reasons** does not work with **PSDscRunAsCredential**
27+
28+
When using the built-in parameter **PSDscRunAsCredential** the read-only
29+
property **Reasons** will return empty values for the properties **Code**
30+
and **Phrase**. The built-in property **PSDscRunAsCredential** does not work
31+
together with class-based resources that using advanced type like the parameter
32+
**Reasons** have.
33+
34+
### Using **Credential** property
35+
36+
SQL Authentication and Group Managed Service Accounts is not supported as
37+
impersonation credentials. Currently only Windows Integrated Security is
38+
supported to use as credentials.
39+
40+
For Windows Authentication the username must either be provided with the User
41+
Principal Name (UPN), e.g. `[email protected]` or if using non-domain
42+
(for example a local Windows Server account) account the username must be
43+
provided without the NetBIOS name, e.g. `username`. Using the NetBIOS name, e.g
44+
using the format `DOMAIN\username` will not work.
45+
46+
See more information in [Credential Overview](https://github.com/dsccommunity/SqlServerDsc/wiki/CredentialOverview).
47+
48+
.PARAMETER Name
49+
The name of the _SQL Server Agent_ alert.
50+
51+
.PARAMETER Ensure
52+
Specifies if the _SQL Server Agent_ alert should be present or absent.
53+
Default value is `'Present'`.
54+
55+
.PARAMETER Severity
56+
The severity of the _SQL Server Agent_ alert. Valid range is 0 to 25.
57+
Cannot be used together with **MessageId**.
58+
59+
.PARAMETER MessageId
60+
The message id of the _SQL Server Agent_ alert. Valid range is 0 to 2147483647.
61+
Cannot be used together with **Severity**.
62+
63+
.EXAMPLE
64+
Invoke-DscResource -ModuleName SqlServerDsc -Name SqlAgentAlert -Method Get -Property @{
65+
InstanceName = 'MSSQLSERVER'
66+
Name = 'Alert1'
67+
}
68+
69+
This example shows how to get the current state of the _SQL Server Agent_
70+
alert named **Alert1**.
71+
72+
.EXAMPLE
73+
Invoke-DscResource -ModuleName SqlServerDsc -Name SqlAgentAlert -Method Test -Property @{
74+
InstanceName = 'MSSQLSERVER'
75+
Name = 'Alert1'
76+
Ensure = 'Present'
77+
Severity = 16
78+
}
79+
80+
This example shows how to test if the _SQL Server Agent_ alert named
81+
**Alert1** is in the desired state.
82+
83+
.EXAMPLE
84+
Invoke-DscResource -ModuleName SqlServerDsc -Name SqlAgentAlert -Method Set -Property @{
85+
InstanceName = 'MSSQLSERVER'
86+
Name = 'Alert1'
87+
Ensure = 'Present'
88+
Severity = 16
89+
}
90+
91+
This example shows how to set the desired state for the _SQL Server Agent_
92+
alert named **Alert1** with severity level 16.
93+
94+
.EXAMPLE
95+
Invoke-DscResource -ModuleName SqlServerDsc -Name SqlAgentAlert -Method Set -Property @{
96+
InstanceName = 'MSSQLSERVER'
97+
Name = 'Alert1'
98+
Ensure = 'Present'
99+
MessageId = 50001
100+
}
101+
102+
This example shows how to set the desired state for the _SQL Server Agent_
103+
alert named **Alert1** with message ID 50001.
104+
105+
.EXAMPLE
106+
Invoke-DscResource -ModuleName SqlServerDsc -Name SqlAgentAlert -Method Set -Property @{
107+
InstanceName = 'MSSQLSERVER'
108+
Name = 'Alert1'
109+
Ensure = 'Absent'
110+
}
111+
112+
This example shows how to remove the _SQL Server Agent_ alert named
113+
**Alert1**.
114+
#>
115+
[DscResource(RunAsCredential = 'Optional')]
116+
class SqlAgentAlert : SqlResourceBase
117+
{
118+
[DscProperty(Key)]
119+
[System.String]
120+
$Name
121+
122+
[DscProperty()]
123+
[ValidateSet('Present', 'Absent')]
124+
[System.String]
125+
$Ensure = 'Present'
126+
127+
[DscProperty()]
128+
[ValidateRange(0, 25)]
129+
[Nullable[System.Int32]]
130+
$Severity
131+
132+
[DscProperty()]
133+
[ValidateRange(0, 2147483647)]
134+
[Nullable[System.Int32]]
135+
$MessageId
136+
137+
SqlAgentAlert () : base ()
138+
{
139+
# Property names that cannot be enforced, e.g. Ensure
140+
$this.ExcludeDscProperties = @()
141+
}
142+
143+
[SqlAgentAlert] Get()
144+
{
145+
# Call base implementation to get current state
146+
$currentState = ([ResourceBase] $this).Get()
147+
148+
return $currentState
149+
}
150+
151+
[System.Boolean] Test()
152+
{
153+
# Call base implementation to test current state
154+
$inDesiredState = ([ResourceBase] $this).Test()
155+
156+
return $inDesiredState
157+
}
158+
159+
[void] Set()
160+
{
161+
# Call base implementation to set desired state
162+
([ResourceBase] $this).Set()
163+
}
164+
165+
hidden [void] AssertProperties([System.Collections.Hashtable] $properties)
166+
{
167+
# Validate that both Severity and MessageId are not specified
168+
Assert-BoundParameter -BoundParameterList $properties -MutuallyExclusiveList1 @('Severity') -MutuallyExclusiveList2 @('MessageId')
169+
}
170+
171+
hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties)
172+
{
173+
$serverObject = $this.GetServerObject()
174+
175+
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_GettingCurrentState -f $this.Name, $this.InstanceName)
176+
177+
$currentState = @{
178+
InstanceName = $this.InstanceName
179+
ServerName = $this.ServerName
180+
Name = $this.Name
181+
Ensure = 'Absent'
182+
}
183+
184+
$alertObject = $serverObject | Get-SqlDscAgentAlert -Name $this.Name -ErrorAction 'SilentlyContinue'
185+
186+
if ($alertObject)
187+
{
188+
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_AlertExists -f $this.Name)
189+
190+
$currentState.Ensure = 'Present'
191+
192+
# Get the current severity and message ID
193+
if ($alertObject.Severity -gt 0)
194+
{
195+
$currentState.Severity = $alertObject.Severity
196+
}
197+
198+
if ($alertObject.MessageId -gt 0)
199+
{
200+
$currentState.MessageId = $alertObject.MessageId
201+
}
202+
}
203+
else
204+
{
205+
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_AlertDoesNotExist -f $this.Name)
206+
}
207+
208+
return $currentState
209+
}
210+
211+
hidden [void] Modify([System.Collections.Hashtable] $properties)
212+
{
213+
$serverObject = $this.GetServerObject()
214+
215+
if ($this.Ensure -eq 'Present')
216+
{
217+
$alertObject = $serverObject | Get-SqlDscAgentAlert -Name $this.Name -ErrorAction 'SilentlyContinue'
218+
219+
if ($null -eq $alertObject)
220+
{
221+
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_CreatingAlert -f $this.Name)
222+
223+
$newAlertParameters = @{
224+
ServerObject = $serverObject
225+
Name = $this.Name
226+
ErrorAction = 'Stop'
227+
}
228+
229+
if ($properties.ContainsKey('Severity'))
230+
{
231+
$newAlertParameters.Severity = $properties.Severity
232+
}
233+
234+
if ($properties.ContainsKey('MessageId'))
235+
{
236+
$newAlertParameters.MessageId = $properties.MessageId
237+
}
238+
239+
$null = New-SqlDscAgentAlert @newAlertParameters
240+
}
241+
else
242+
{
243+
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_UpdatingAlert -f $this.Name)
244+
245+
$setAlertParameters = @{
246+
AlertObject = $alertObject
247+
ErrorAction = 'Stop'
248+
}
249+
250+
$needsUpdate = $false
251+
252+
if ($properties.ContainsKey('Severity') -and $alertObject.Severity -ne $properties.Severity)
253+
{
254+
$setAlertParameters.Severity = $properties.Severity
255+
$needsUpdate = $true
256+
}
257+
258+
if ($properties.ContainsKey('MessageId') -and $alertObject.MessageId -ne $properties.MessageId)
259+
{
260+
$setAlertParameters.MessageId = $properties.MessageId
261+
$needsUpdate = $true
262+
}
263+
264+
if ($needsUpdate)
265+
{
266+
$null = Set-SqlDscAgentAlert @setAlertParameters
267+
}
268+
else
269+
{
270+
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_NoChangesNeeded -f $this.Name)
271+
}
272+
}
273+
}
274+
else # Ensure = 'Absent'
275+
{
276+
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_RemovingAlert -f $this.Name)
277+
278+
$null = $serverObject | Remove-SqlDscAgentAlert -Name $this.Name -Force -ErrorAction 'Stop'
279+
}
280+
}
281+
}

0 commit comments

Comments
 (0)