Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4d5b237
`SqlAgentAlert`: Refactor to class-bases resource
johlju Aug 24, 2025
4897a08
Fix typo in DSC class resource guidelines for ExcludeDscProperties co…
johlju Aug 25, 2025
11a2f00
Remove unit tests for DSC_SqlAgentAlert resource
johlju Aug 26, 2025
e62febd
Update SqlServerDsc guidelines to clarify resource inheritance and pr…
johlju Aug 26, 2025
4dfbc14
Update ExcludeDscProperties in SqlAgentAlert constructor to include I…
johlju Aug 26, 2025
642a742
Remove unnecessary blank lines from DSC_SqlAgentAlert_Remove_Config c…
johlju Aug 26, 2025
d74eedd
Update integration test to verify resource name against Configuration…
johlju Aug 26, 2025
9398b1d
Update AssertProperties method to validate Severity and MessageId par…
johlju Aug 26, 2025
9a5cba3
Fix AssertProperties method to validate Ensure property from input pa…
johlju Aug 26, 2025
017f3a8
Enhance SqlAgentAlert to restrict Severity and MessageId when Ensure …
johlju Aug 26, 2025
0afbd30
Clarify guideline on nullable property types in DSC class-based resou…
johlju Aug 26, 2025
c4050a5
Clarify guidelines for nullable property types in DSC class-based res…
johlju Aug 26, 2025
207ce9c
Add support for MessageId in SqlAgentAlert configuration and tests
johlju Aug 26, 2025
5856af5
Update module name placeholders in integration, localization, and uni…
johlju Aug 26, 2025
1c74791
Refactor AI instructions header to generalize project guidance
johlju Aug 26, 2025
670f853
Clarify hashtable formatting guideline for multi-line properties
johlju Aug 26, 2025
6d1226d
Add "RAISERROR" to cSpell words list in settings.json
johlju Aug 26, 2025
d75a5da
Enhance SQL Agent alert configurations to utilize MessageId with RAIS…
johlju Aug 26, 2025
2d0a7da
Add optional encryption parameter to SQL Agent alert configurations
johlju Aug 26, 2025
8a08ce4
Enhance PowerShell guidelines with output stream usage and backtick l…
johlju Aug 26, 2025
c4263f1
Correct capitalization in PowerShell guidelines for Write-Verbose usage
johlju Aug 26, 2025
54a02bf
Fix variable name in BeforeAll block for consistency in integration t…
johlju Aug 27, 2025
b3a7de8
Fix variable name in BeforeAll block for consistency in unit test gui…
johlju Aug 27, 2025
8fceb2a
Refactor naming section in Pester guidelines for clarity and consistency
johlju Aug 27, 2025
809953f
Enhance SqlAgentAlert validation logic in AssertProperties method and…
johlju Aug 28, 2025
05fe912
Update SqlAgentAlert documentation and adjust validation ranges for S…
johlju Aug 28, 2025
cd6e952
Update SqlAgentAlert parameter documentation to correct valid ranges …
johlju Aug 28, 2025
b60438e
Fix constructor definition in DSC class guidelines to include $PSScri…
johlju Aug 28, 2025
f898566
Update SqlServerDsc guidelines to clarify resource inheritance and co…
johlju Aug 28, 2025
51175ed
Refine documentation for SqlAgentAlert to improve clarity on alert ty…
johlju Aug 28, 2025
3354094
Refactor SqlAgentAlert to streamline absence validation for Severity …
johlju Aug 28, 2025
5c97552
Fix formatting in changelog guidelines for issue references
johlju Aug 28, 2025
0a2fba5
Add assertion for bound parameters in SqlAgentAlert Set method
johlju Aug 28, 2025
a245ba2
Add tests for Modify() method in SqlAgentAlert to validate alert updates
johlju Aug 29, 2025
250b7f0
Update build and test workflow instructions for clarity and consistency
johlju Aug 29, 2025
87fec7e
Refactor test descriptions in SqlAgentAlert.Tests.ps1 for clarity and…
johlju Aug 29, 2025
2f86af9
Refactor test assertions in SqlAgentAlert.Tests.ps1 to use assignment…
johlju Aug 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/instructions/SqlServerDsc-guidelines.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ applyTo: "**"

## Resources
- Database Engine resources: inherit `SqlResourceBase`
- `SqlResourceBase` provides: `InstanceName`, `ServerName`, `Credential`, `Reasons`, `GetServerObject()`
- Inheriting `SqlResourceBase`; add `InstanceName`, `ServerName`, and `Credential` to `$this.ExcludeDscProperties`
- `SqlResourceBase` provides: `InstanceName`, `ServerName`, `Credential`, `Reasons`, `GetServerObject()`

## SQL Server Interaction
- Always prefer SMO over T-SQL
Expand All @@ -34,6 +35,7 @@ applyTo: "**"
- Choose the appropriate group number based on the required dependencies

## Unit tests
- When unit test uses SMO types, ensure they are properly stubbed in SMO.cs
- Load stub types from SMO.cs in unit test files, e.g. `Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs"`
- After changing SMO stub types, run tests in a new PowerShell session for changes to take effect.
- When unit test tests classes or commands that contain SMO types, e.g. `[Microsoft.SqlServer.Management.Smo.*]`
- Ensure they are properly stubbed in SMO.cs
- Load SMO stub types from SMO.cs in unit test files, e.g. `Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs"`
- After changing SMO stub types, run tests in a new PowerShell session for changes to take effect.
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ applyTo: "source/[cC]lasses/**/*.ps1"
- Decoration: `[DscResource(RunAsCredential = 'Optional')]` (replace with `'Mandatory'` if required)
- Inheritance: Must inherit `ResourceBase` (part of module DscResource.Base)
- `$this.localizedData` hashtable auto-populated by `ResourceBase` from localization file
- value-type properties: Use `[Nullable[{FullTypeName}]]` (e.g., `[Nullable[System.Int32]]`)

## Required constructor

```powershell
MyResourceName () : base ($PSScriptRoot)
MyResourceName () : base ()
{
# Property names where state cannot be enforced, e.g Ensure
# Property names where state cannot be enforced, e.g. IsSingleInstance, Force
$this.ExcludeDscProperties = @()
}
```
Expand Down Expand Up @@ -78,3 +79,19 @@ hidden [void] NormalizeProperties([System.Collections.Hashtable] $properties)
# Normalize user-provided properties, $properties contains user assigned values
}
```

## Required comment-based help

Add to .DESCRIPTION section:
- `## Requirements`: List minimum requirements
- `## 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}).`

## Error Handling
- Use `try/catch` blocks to handle exceptions
- Do not use `throw` for terminating errors, use `New-*Exception` commands:
- [`New‑InvalidDataException`](https://github.com/dsccommunity/DscResource.Common/wiki/New%E2%80%91InvalidDataException)
- [`New-ArgumentException`](https://github.com/dsccommunity/DscResource.Common/wiki/New%E2%80%91ArgumentException)
- [`New-InvalidOperationException`](https://github.com/dsccommunity/DscResource.Common/wiki/New%E2%80%91InvalidOperationException)
- [`New-ObjectNotFoundException`](https://github.com/dsccommunity/DscResource.Common/wiki/New%E2%80%91ObjectNotFoundException)
- [`New-InvalidResultException`](https://github.com/dsccommunity/DscResource.Common/wiki/New%E2%80%91InvalidResultException)
- [`New-NotImplementedException`](https://github.com/dsccommunity/DscResource.Common/wiki/New%E2%80%91NotImplementedException)
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ applyTo: "**/*.[Tt]ests.ps1"
- `It` descriptions start with 'Should', must not contain 'when'
- Mock variables prefix: 'mock'
- Prefer `-BeTrue`/`-BeFalse` over `-Be $true`/`-Be $false`
- Never use `Assert-MockCalled`, use `Should -Invoke` instead
- No `Should -Not -Throw` - invoke commands directly
- Never add an empty `-MockWith` block
- Omit `-MockWith` when returning `$null`
- Set `$PSDefaultParameterValues` for `Mock:ModuleName`, `Should:ModuleName`, `InModuleScope:ModuleName`
- Omit `-ModuleName` parameter on Pester commands
- Never use `Mock` inside `InModuleScope`-block

## File Organization
- Class resources: `tests/Unit/Classes/{Name}.Tests.ps1`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ applyTo: "**/*.ps?(m|d)1"
- For state-changing functions, use `SupportsShouldProcess`
- Place ShouldProcess check immediately before each state-change
- `$PSCmdlet.ShouldProcess` must use required pattern
- Use `$PSCmdlet.ThrowTerminatingError()` for terminating errors, use relevant error category
- Use `$PSCmdlet.ThrowTerminatingError()` for terminating errors (except for classes), use relevant error category
- Use `Write-Error` for non-terminating errors, use relevant error category
- Use `Write-Warning` for warnings
- Use `Write-Debug` for debugging information
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added setup workflow for GitHub Copilot.
- Switch the workflow to use Linux.
- Attempt to unshallow the Copilot branch
- `SqlAgentAlert`
- Added new DSC resource to manage SQL Server Agent alerts.
- Improved AI instructions.
- Enhanced workflow with proper environment variable configuration and DSCv3 verification.
- Fixed environment variable persistence by using $GITHUB_ENV instead of
Expand Down
304 changes: 304 additions & 0 deletions source/Classes/020.SqlAgentAlert.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
<#
.SYNOPSIS
The `SqlAgentAlert` DSC resource is used to create, modify, or remove
_SQL Server Agent_ alerts.

.DESCRIPTION
The `SqlAgentAlert` DSC resource is used to create, modify, or remove
_SQL Server Agent_ alerts.

The built-in parameter **PSDscRunAsCredential** can be used to run the resource
as another user. The resource will then authenticate to the _SQL Server_
instance as that user. It also possible to instead use impersonation by the
parameter **Credential**.

## Requirements

* Target machine must be running Windows Server 2012 or later.
* Target machine must be running SQL Server Database Engine 2012 or later.
* Target machine must have access to the SQLPS PowerShell module or the SqlServer
PowerShell module.

## Known issues

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).

### Property **Reasons** does not work with **PSDscRunAsCredential**

When using the built-in parameter **PSDscRunAsCredential** the read-only
property **Reasons** will return empty values for the properties **Code**
and **Phrase**. The built-in property **PSDscRunAsCredential** does not work
together with class-based resources that using advanced type like the parameter
**Reasons** have.

### Using **Credential** property

SQL Authentication and Group Managed Service Accounts is not supported as
impersonation credentials. Currently only Windows Integrated Security is
supported to use as credentials.

For Windows Authentication the username must either be provided with the User
Principal Name (UPN), e.g. `username@domain.local` or if using non-domain
(for example a local Windows Server account) account the username must be
provided without the NetBIOS name, e.g. `username`. Using the NetBIOS name, e.g
using the format `DOMAIN\username` will not work.

See more information in [Credential Overview](https://github.com/dsccommunity/SqlServerDsc/wiki/CredentialOverview).

.PARAMETER Name
The name of the _SQL Server Agent_ alert.

.PARAMETER Ensure
Specifies if the _SQL Server Agent_ alert should be present or absent.
Default value is `'Present'`.

.PARAMETER Severity
The severity of the _SQL Server Agent_ alert. Valid range is 0 to 25.
Cannot be used together with **MessageId**.

.PARAMETER MessageId
The message id of the _SQL Server Agent_ alert. Valid range is 0 to 2147483647.
Cannot be used together with **Severity**.

.EXAMPLE
Invoke-DscResource -ModuleName SqlServerDsc -Name SqlAgentAlert -Method Get -Property @{
InstanceName = 'MSSQLSERVER'
Name = 'Alert1'
}

This example shows how to get the current state of the _SQL Server Agent_
alert named **Alert1**.

.EXAMPLE
Invoke-DscResource -ModuleName SqlServerDsc -Name SqlAgentAlert -Method Test -Property @{
InstanceName = 'MSSQLSERVER'
Name = 'Alert1'
Ensure = 'Present'
Severity = 16
}

This example shows how to test if the _SQL Server Agent_ alert named
**Alert1** is in the desired state.

.EXAMPLE
Invoke-DscResource -ModuleName SqlServerDsc -Name SqlAgentAlert -Method Set -Property @{
InstanceName = 'MSSQLSERVER'
Name = 'Alert1'
Ensure = 'Present'
Severity = 16
}

This example shows how to set the desired state for the _SQL Server Agent_
alert named **Alert1** with severity level 16.

.EXAMPLE
Invoke-DscResource -ModuleName SqlServerDsc -Name SqlAgentAlert -Method Set -Property @{
InstanceName = 'MSSQLSERVER'
Name = 'Alert1'
Ensure = 'Present'
MessageId = 50001
}

This example shows how to set the desired state for the _SQL Server Agent_
alert named **Alert1** with message ID 50001.

.EXAMPLE
Invoke-DscResource -ModuleName SqlServerDsc -Name SqlAgentAlert -Method Set -Property @{
InstanceName = 'MSSQLSERVER'
Name = 'Alert1'
Ensure = 'Absent'
}

This example shows how to remove the _SQL Server Agent_ alert named
**Alert1**.
#>
[DscResource(RunAsCredential = 'Optional')]
class SqlAgentAlert : SqlResourceBase
{
[DscProperty(Key)]
[System.String]
$Name

[DscProperty()]
[ValidateSet('Present', 'Absent')]
[System.String]
$Ensure = 'Present'

[DscProperty()]
[ValidateRange(0, 25)]
[Nullable[System.Int32]]
$Severity

[DscProperty()]
[ValidateRange(0, 2147483647)]
[Nullable[System.Int32]]
$MessageId

SqlAgentAlert () : base ()
{
# Property names that cannot be enforced
$this.ExcludeDscProperties = @(
'InstanceName',
'ServerName',
'Credential'
'Name'
)
}

[SqlAgentAlert] Get()
{
# Call base implementation to get current state
$currentState = ([ResourceBase] $this).Get()

return $currentState
}

[System.Boolean] Test()
{
# Call base implementation to test current state
$inDesiredState = ([ResourceBase] $this).Test()

return $inDesiredState
}

[void] Set()
{
# Call base implementation to set desired state
([ResourceBase] $this).Set()
}

hidden [void] AssertProperties([System.Collections.Hashtable] $properties)
{
# TODO: Waiting for issue: https://github.com/dsccommunity/DscResource.Common/issues/160
if ($properties.Ensure -eq 'Present')
{
# Validate that at least one of Severity or MessageId is specified
# TODO: Waiting for issue: https://github.com/dsccommunity/DscResource.Common/issues/161
#Assert-BoundParameter -BoundParameterList $properties -AtLeastOneList @('Severity', 'MessageId')

# Validate that both Severity and MessageId are not specified
Assert-BoundParameter -BoundParameterList $properties -MutuallyExclusiveList1 @('Severity') -MutuallyExclusiveList2 @('MessageId')
}
else
{
# When Ensure is 'Absent', Severity and MessageId must not be set
if ($properties.ContainsKey('Severity') -or $properties.ContainsKey('MessageId'))
{
$errorMessage = $this.localizedData.SqlAgentAlert_SeverityOrMessageIdNotAllowedWhenAbsent

New-InvalidArgumentException -ArgumentName 'Severity, MessageId' -Message $errorMessage
}
}
}

hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties)
{
$serverObject = $this.GetServerObject()

Write-Verbose -Message ($this.localizedData.SqlAgentAlert_GettingCurrentState -f $this.Name, $this.InstanceName)

$currentState = @{
InstanceName = $this.InstanceName
ServerName = $this.ServerName
Name = $this.Name
Ensure = 'Absent'
}

$alertObject = $serverObject | Get-SqlDscAgentAlert -Name $this.Name -ErrorAction 'SilentlyContinue'

if ($alertObject)
{
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_AlertExists -f $this.Name)

$currentState.Ensure = 'Present'

# Get the current severity and message ID
if ($alertObject.Severity -gt 0)
{
$currentState.Severity = $alertObject.Severity
}

if ($alertObject.MessageId -gt 0)
{
$currentState.MessageId = $alertObject.MessageId
}
}
else
{
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_AlertDoesNotExist -f $this.Name)
}

return $currentState
}

hidden [void] Modify([System.Collections.Hashtable] $properties)
{
$serverObject = $this.GetServerObject()

if ($this.Ensure -eq 'Present')
{
$alertObject = $serverObject | Get-SqlDscAgentAlert -Name $this.Name -ErrorAction 'SilentlyContinue'

if ($null -eq $alertObject)
{
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_CreatingAlert -f $this.Name)

$newAlertParameters = @{
ServerObject = $serverObject
Name = $this.Name
ErrorAction = 'Stop'
}

if ($properties.ContainsKey('Severity'))
{
$newAlertParameters.Severity = $properties.Severity
}

if ($properties.ContainsKey('MessageId'))
{
$newAlertParameters.MessageId = $properties.MessageId
}

$null = New-SqlDscAgentAlert @newAlertParameters
}
else
{
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_UpdatingAlert -f $this.Name)

$setAlertParameters = @{
AlertObject = $alertObject
ErrorAction = 'Stop'
}

$needsUpdate = $false

if ($properties.ContainsKey('Severity') -and $alertObject.Severity -ne $properties.Severity)
{
$setAlertParameters.Severity = $properties.Severity
$needsUpdate = $true
}

if ($properties.ContainsKey('MessageId') -and $alertObject.MessageId -ne $properties.MessageId)
{
$setAlertParameters.MessageId = $properties.MessageId
$needsUpdate = $true
}

if ($needsUpdate)
{
$null = Set-SqlDscAgentAlert @setAlertParameters
}
else
{
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_NoChangesNeeded -f $this.Name)
}
}
}
else # Ensure = 'Absent'
{
Write-Verbose -Message ($this.localizedData.SqlAgentAlert_RemovingAlert -f $this.Name)

$null = $serverObject | Remove-SqlDscAgentAlert -Name $this.Name -Force -ErrorAction 'Stop'
}
}
}
Loading
Loading