From b24bb04f1906250e12d88d05ed5c2ecf06b855c9 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 21 Jun 2022 20:08:25 +0200 Subject: [PATCH 01/98] Initial class-based resource --- source/Classes/001.ResourceBase.ps1 | 211 +++++++++++++++++++ source/Classes/002.DatabasePermission.ps1 | 18 ++ source/Classes/002.Reason.ps1 | 10 + source/Classes/003.SqlDatabasePermission.ps1 | 129 ++++++++++++ source/Enum/1.DatabasePermissionState.ps1 | 6 + source/Enum/1.Ensure.ps1 | 5 + 6 files changed, 379 insertions(+) create mode 100644 source/Classes/001.ResourceBase.ps1 create mode 100644 source/Classes/002.DatabasePermission.ps1 create mode 100644 source/Classes/002.Reason.ps1 create mode 100644 source/Classes/003.SqlDatabasePermission.ps1 create mode 100644 source/Enum/1.DatabasePermissionState.ps1 create mode 100644 source/Enum/1.Ensure.ps1 diff --git a/source/Classes/001.ResourceBase.ps1 b/source/Classes/001.ResourceBase.ps1 new file mode 100644 index 000000000..3d423ab96 --- /dev/null +++ b/source/Classes/001.ResourceBase.ps1 @@ -0,0 +1,211 @@ +<# + .SYNOPSIS + A class with methods that are equal for all class-based resources. + + .DESCRIPTION + A class with methods that are equal for all class-based resources. + + .NOTES + This class should not contain any DSC properties. +#> + +class ResourceBase +{ + # Hidden property for holding localization strings + hidden [System.Collections.Hashtable] $localizedData = @{} + + # Default constructor + ResourceBase() + { + $this.localizedData = Get-LocalizedDataRecursive -ClassName ($this | Get-ClassName -Recurse) + } + + [ResourceBase] Get() + { + $this.Assert() + + Write-Verbose -Message ($this.localizedData.GetCurrentState -f $this.DnsServer, $this.GetType().Name) + + # Get all key properties. + $keyProperty = $this | + Get-Member -MemberType 'Property' | + Select-Object -ExpandProperty Name | + Where-Object -FilterScript { + $this.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'Key' }).NamedArguments.TypedValue.Value -eq $true + } + + $getParameters = @{} + + # Set each key property to its value (property DnsServer is handled below). + $keyProperty | + Where-Object -FilterScript { + $_ -ne 'DnsServer' + } | + ForEach-Object -Process { + $getParameters[$_] = $this.$_ + } + + # Set ComputerName depending on value of DnsServer. + if ($this.DnsServer -ne 'localhost') + { + $getParameters['ComputerName'] = $this.DnsServer + } + + $getCurrentStateResult = $this.GetCurrentState($getParameters) + + $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) + + foreach ($propertyName in $this.PSObject.Properties.Name) + { + if ($propertyName -in @($getCurrentStateResult.PSObject.Properties.Name)) + { + $dscResourceObject.$propertyName = $getCurrentStateResult.$propertyName + } + } + + # Always set this as it won't be in the $getCurrentStateResult + $dscResourceObject.DnsServer = $this.DnsServer + + # Return properties. + return $dscResourceObject + } + + [void] Set() + { + $this.Assert() + + Write-Verbose -Message ($this.localizedData.SetDesiredState -f $this.DnsServer, $this.GetType().Name) + + # Call the Compare method to get enforced properties that are not in desired state. + $propertiesNotInDesiredState = $this.Compare() + + if ($propertiesNotInDesiredState) + { + $propertiesToModify = $this.GetDesiredStateForSplatting($propertiesNotInDesiredState) + + $propertiesToModify.Keys | ForEach-Object -Process { + Write-Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_, $this.GetType().Name) + } + + if ($this.DnsServer -ne 'localhost') + { + $propertiesToModify['ComputerName'] = $this.DnsServer + } + + <# + Call the Modify() method with the properties that should be enforced + and was not in desired state. + #> + $this.Modify($propertiesToModify) + } + else + { + Write-Verbose -Message $this.localizedData.NoPropertiesToSet + } + } + + [System.Boolean] Test() + { + Write-Verbose -Message ($this.localizedData.TestDesiredState -f $this.DnsServer, $this.GetType().Name) + + $this.Assert() + + $isInDesiredState = $true + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + $propertiesNotInDesiredState = $this.Compare() + + if ($propertiesNotInDesiredState) + { + $isInDesiredState = $false + } + + if ($isInDesiredState) + { + Write-Verbose -Message ($this.localizedData.InDesiredState -f $this.DnsServer, $this.GetType().Name) + } + else + { + Write-Verbose -Message ($this.localizedData.NotInDesiredState -f $this.DnsServer, $this.GetType().Name) + } + + return $isInDesiredState + } + + <# + Returns a hashtable containing all properties that should be enforced. + This method should normally not be overridden. + #> + hidden [System.Collections.Hashtable[]] Compare() + { + $currentState = $this.Get() | ConvertFrom-DscResourceInstance + $desiredState = $this | ConvertFrom-DscResourceInstance + + <# + Remove properties that have $null as the value, and remove read + properties so that there is no chance to compare those. + #> + @($desiredState.Keys) | ForEach-Object -Process { + $isReadProperty = $this.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'NotConfigurable' }).NamedArguments.TypedValue.Value -eq $true + + if ($isReadProperty -or $null -eq $desiredState[$_]) + { + $desiredState.Remove($_) + } + } + + $CompareDscParameterState = @{ + CurrentValues = $currentState + DesiredValues = $desiredState + Properties = $desiredState.Keys + ExcludeProperties = @('DnsServer') + IncludeValue = $true + } + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + return (Compare-DscParameterState @CompareDscParameterState) + } + + # Returns a hashtable containing all properties that should be enforced. + hidden [System.Collections.Hashtable] GetDesiredStateForSplatting([System.Collections.Hashtable[]] $Properties) + { + $desiredState = @{} + + $Properties | ForEach-Object -Process { + $desiredState[$_.Property] = $_.ExpectedValue + } + + return $desiredState + } + + # This method should normally not be overridden. + hidden [void] Assert() + { + Assert-Module -ModuleName 'DnsServer' + + $this.AssertProperties() + } + + # This method can be overridden if resource specific asserts are needed. + hidden [void] AssertProperties() + { + } + + # This method must be overridden by a resource. + hidden [void] Modify([System.Collections.Hashtable] $properties) + { + throw $this.localizedData.ModifyMethodNotImplemented + } + + # This method must be overridden by a resource. + hidden [Microsoft.Management.Infrastructure.CimInstance] GetCurrentState([System.Collections.Hashtable] $properties) + { + throw $this.localizedData.GetCurrentStateMethodNotImplemented + } +} diff --git a/source/Classes/002.DatabasePermission.ps1 b/source/Classes/002.DatabasePermission.ps1 new file mode 100644 index 000000000..5d22e6b4c --- /dev/null +++ b/source/Classes/002.DatabasePermission.ps1 @@ -0,0 +1,18 @@ +<# + .PARAMETER State + The state of the permission. + + .PARAMETER Permission + The permissions to be granted or denied for the user in the database. +#> + +class DatabasePermission +{ + [DscProperty(Key)] + [DatabasePermissionState] + $State + + [DscProperty(Mandatory)] + [System.String] + $Permission +} diff --git a/source/Classes/002.Reason.ps1 b/source/Classes/002.Reason.ps1 new file mode 100644 index 000000000..086cee3dd --- /dev/null +++ b/source/Classes/002.Reason.ps1 @@ -0,0 +1,10 @@ +class Reason +{ + [DscProperty()] + [System.String] + $Code + + [DscProperty()] + [System.String] + $Phrase +} diff --git a/source/Classes/003.SqlDatabasePermission.ps1 b/source/Classes/003.SqlDatabasePermission.ps1 new file mode 100644 index 000000000..354ae1f18 --- /dev/null +++ b/source/Classes/003.SqlDatabasePermission.ps1 @@ -0,0 +1,129 @@ +<# + .SYNOPSIS + The `SqlDatabasePermission` DSC resource is used to grant, deny or revoke + permissions for a user in a database + + .DESCRIPTION + The `SqlDatabasePermission` DSC resource is used to grant, deny or revoke + permissions for a user in a database. For more information about permissions, + please read the article [Permissions (Database Engine)](https://docs.microsoft.com/en-us/sql/relational-databases/security/permissions-database-engine). + + >**Note:** When revoking permission with PermissionState 'GrantWithGrant', both the + >grantee and _all the other users the grantee has granted the same permission to_, + >will also get their permission revoked. + + Valid permission names can be found in the article [DatabasePermissionSet Class properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.databasepermissionset#properties). + + ## Requirements + + * Target machine must be running Windows Server 2012 or later. + * Target machine must be running SQL Server Database Engine 2012 or later. + + ## 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+SqlDatabasePermission). + + .PARAMETER InstanceName + The name of the SQL Server instance to be configured. Default value is + 'MSSQLSERVER'. + + .PARAMETER DatabaseName + The name of the database. + + .PARAMETER Name + The name of the user that should be granted or denied the permission. + + .PARAMETER ServerName + The host name of the SQL Server to be configured. Default value is the + current computer name. + + .PARAMETER Permission + The database permission to enforce. + + .PARAMETER Ensure + If the permission should be granted ('Present') or revoked ('Absent'). + + .PARAMETER Reasons + Returns the reason a property is not in desired state. +#> + +[DscResource()] +class SqlDatabasePermission : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $InstanceName + + [DscProperty(Key)] + [System.String] + $DatabaseName + + [DscProperty(Key)] + [System.String] + $Name + + [DscProperty()] + [System.String] + $ServerName + + [DscProperty(Mandatory)] + [DatabasePermission[]] + $Permission + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [SqlDatabasePermission] Get() + { + # Call the base method to return the properties. + return ([ResourceBase] $this).Get() + } + + # Base method Get() call this method to get the current state as a CimInstance. + [Microsoft.Management.Infrastructure.CimInstance] GetCurrentState([System.Collections.Hashtable] $properties) + { + return (Get-DnsServerDsSetting @properties) + } + + [void] Set() + { + # Call the base method to enforce the properties. + ([ResourceBase] $this).Set() + } + + <# + Base method Set() call this method with the properties that should be + enforced and that are not in desired state. + #> + [void] Modify([System.Collections.Hashtable] $properties) + { + Set-DnsServerDsSetting @properties + } + + [System.Boolean] Test() + { + # Call the base method to test all of the properties that should be enforced. + return ([ResourceBase] $this).Test() + } + + hidden [void] AssertProperties() + { + @( + 'DirectoryPartitionAutoEnlistInterval', + 'TombstoneInterval' + ) | ForEach-Object -Process { + $valueToConvert = $this.$_ + + # Only evaluate properties that have a value. + if ($null -ne $valueToConvert) + { + Assert-TimeSpan -PropertyName $_ -Value $valueToConvert -Minimum '0.00:00:00' + } + } + } +} diff --git a/source/Enum/1.DatabasePermissionState.ps1 b/source/Enum/1.DatabasePermissionState.ps1 new file mode 100644 index 000000000..c81b24f60 --- /dev/null +++ b/source/Enum/1.DatabasePermissionState.ps1 @@ -0,0 +1,6 @@ +enum DatabasePermissionState +{ + Grant + Deny + GrantWithGrant +} diff --git a/source/Enum/1.Ensure.ps1 b/source/Enum/1.Ensure.ps1 new file mode 100644 index 000000000..9a57804c3 --- /dev/null +++ b/source/Enum/1.Ensure.ps1 @@ -0,0 +1,5 @@ +enum Ensure +{ + Present + Absent +} From 42b18862cbbf721a813a866f5b73e075da6185e4 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 22 Jun 2022 13:08:19 +0200 Subject: [PATCH 02/98] Fix missing strings and private functions --- source/Private/Get-ClassName.ps1 | 43 +++++++++++ source/Private/Get-LocalizedDataRecursive.ps1 | 75 +++++++++++++++++++ source/en-US/ResourceBase.strings.psd1 | 17 +++++ .../en-US/SqlDatabasePermission.strings.psd1 | 14 ++++ 4 files changed, 149 insertions(+) create mode 100644 source/Private/Get-ClassName.ps1 create mode 100644 source/Private/Get-LocalizedDataRecursive.ps1 create mode 100644 source/en-US/ResourceBase.strings.psd1 create mode 100644 source/en-US/SqlDatabasePermission.strings.psd1 diff --git a/source/Private/Get-ClassName.ps1 b/source/Private/Get-ClassName.ps1 new file mode 100644 index 000000000..a025d1f86 --- /dev/null +++ b/source/Private/Get-ClassName.ps1 @@ -0,0 +1,43 @@ +<# + .SYNOPSIS + Get the class name of the passed object, and optional an array with + all inherited classes. + + .PARAMETER InputObject + The object to be evaluated. + + .OUTPUTS + Returns a string array with at least one item. +#> +function Get-ClassName +{ + [CmdletBinding()] + [OutputType([System.Object[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Recurse + ) + + # Create a list of the inherited class names + $class = @($InputObject.GetType().FullName) + + if ($Recurse.IsPresent) + { + $parentClass = $InputObject.GetType().BaseType + + while ($parentClass -ne [System.Object]) + { + $class += $parentClass.FullName + + $parentClass = $parentClass.BaseType + } + } + + return ,$class +} diff --git a/source/Private/Get-LocalizedDataRecursive.ps1 b/source/Private/Get-LocalizedDataRecursive.ps1 new file mode 100644 index 000000000..6f05a055a --- /dev/null +++ b/source/Private/Get-LocalizedDataRecursive.ps1 @@ -0,0 +1,75 @@ +<# + .SYNOPSIS + Get the localization strings data from one or more localization string files. + This can be used in classes to be able to inherit localization strings + from one or more parent (base) classes. + + The order of class names passed to parameter `ClassName` determines the order + of importing localization string files. First entry's localization string file + will be imported first, then next entry's localization string file, and so on. + If the second (or any consecutive) entry's localization string file contain a + localization string key that existed in a previous imported localization string + file that localization string key will be ignored. Making it possible for a + child class to override localization strings from one or more parent (base) + classes. + + .PARAMETER ClassName + An array of class names, normally provided by `Get-ClassName -Recurse`. + + .OUTPUTS + Returns a string array with at least one item. +#> +function Get-LocalizedDataRecursive +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.String[]] + $ClassName + ) + + begin + { + $localizedData = @{} + } + + process + { + foreach ($name in $ClassName) + { + if ($name -match '\.psd1') + { + # Assume we got full file name. + $localizationFileName = $name + } + else + { + # Assume we only got class name. + $localizationFileName = '{0}.strings.psd1' -f $name + } + + Write-Debug -Message ('Importing localization data from {0}' -f $localizationFileName) + + # Get localized data for the class + $classLocalizationStrings = Get-LocalizedData -DefaultUICulture 'en-US' -FileName $localizationFileName -ErrorAction 'Stop' + + # Append only previously unspecified keys in the localization data + foreach ($key in $classLocalizationStrings.Keys) + { + if (-not $localizedData.ContainsKey($key)) + { + $localizedData[$key] = $classLocalizationStrings[$key] + } + } + } + } + + end + { + Write-Debug -Message ('Localization data: {0}' -f ($localizedData | ConvertTo-JSON)) + + return $localizedData + } +} diff --git a/source/en-US/ResourceBase.strings.psd1 b/source/en-US/ResourceBase.strings.psd1 new file mode 100644 index 000000000..f73e82d47 --- /dev/null +++ b/source/en-US/ResourceBase.strings.psd1 @@ -0,0 +1,17 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + class ResourceBase. +#> + +ConvertFrom-StringData @' + GetCurrentState = Getting the current state of {1} for the server '{0}'. (RB0001) + TestDesiredState = Determining the current state of {1} for the server '{0}'. (RB0002) + SetDesiredState = Setting the desired state of {1} for the server '{0}'. (RB0003) + NotInDesiredState = The {1} for the server '{0}' is not in desired state. (RB0004) + InDesiredState = The {1} for the server '{0}' is in desired state. (RB0005) + SetProperty = The {2} property '{0}' will be set to '{1}'. (RB0006) + NoPropertiesToSet = All properties are in desired state. (RB0007) + ModifyMethodNotImplemented = An override for the method Modify() is not implemented in the resource. (RB0008) + GetCurrentStateMethodNotImplemented = An override for the method GetCurrentState() is not implemented in the resource. (RB0009) +'@ diff --git a/source/en-US/SqlDatabasePermission.strings.psd1 b/source/en-US/SqlDatabasePermission.strings.psd1 new file mode 100644 index 000000000..69ceec5e3 --- /dev/null +++ b/source/en-US/SqlDatabasePermission.strings.psd1 @@ -0,0 +1,14 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource SqlDatabasePermission. +#> + +ConvertFrom-StringData @' + GetCurrentState = Getting the current state of the database permssions for the user '{0}'. (SDP0001) + TestDesiredState = Determining the current state of the database permssions for the user '{0}'. (SDP0002) + SetDesiredState = Setting the desired state for the database permssions for the user '{0}'. (SDP0003) + NotInDesiredState = The database permssions for the user '{0}' is not in desired state. (SDP0004) + InDesiredState = The database permssions for the user '{0}' is in desired state. (SDP0005) + SetProperty = The permission '{0}' will be set to '{1}'. (SDP0006) +'@ From 18d5a408c96ddd740434f656378d3013dcad6e09 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 26 Jun 2022 12:33:36 +0200 Subject: [PATCH 03/98] remove MOF resource schema and README.md --- .../DSC_SqlDatabasePermission.schema.mof | 11 ---------- .../DSC_SqlDatabasePermission/README.md | 20 ------------------- 2 files changed, 31 deletions(-) delete mode 100644 source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.schema.mof delete mode 100644 source/DSCResources/DSC_SqlDatabasePermission/README.md diff --git a/source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.schema.mof b/source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.schema.mof deleted file mode 100644 index 122d072b6..000000000 --- a/source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.schema.mof +++ /dev/null @@ -1,11 +0,0 @@ -[ClassVersion("1.0.0.0"), FriendlyName("SqlDatabasePermission")] -class DSC_SqlDatabasePermission : OMI_BaseResource -{ - [Key, Description("The name of the database.")] String DatabaseName; - [Key, Description("The name of the user that should be granted or denied the permission.")] String Name; - [Key, Description("The state of the permission."), ValueMap{"Grant","Deny","GrantWithGrant"}, Values{"Grant","Deny","GrantWithGrant"}] String PermissionState; - [Key, Description("The name of the _SQL Server_ instance to be configured. Default value is `'MSSQLSERVER'`.")] String InstanceName; - [Required, Description("The permissions to be granted or denied for the user in the database.")] String Permissions[]; - [Write, Description("If the permission should be granted (`'Present'`) or revoked (`'Absent'`)."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Write, Description("The host name of the _SQL Server_ to be configured. Default value is the current computer name.")] String ServerName; -}; diff --git a/source/DSCResources/DSC_SqlDatabasePermission/README.md b/source/DSCResources/DSC_SqlDatabasePermission/README.md deleted file mode 100644 index d18357526..000000000 --- a/source/DSCResources/DSC_SqlDatabasePermission/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Description - -The `SqlDatabasePermission` DSC resource is used to grant, deny or revoke -permissions for a user in a database. For more information about permissions, -please read the article [Permissions (Database Engine)](https://docs.microsoft.com/en-us/sql/relational-databases/security/permissions-database-engine). - ->**Note:** When revoking permission with PermissionState 'GrantWithGrant', both the ->grantee and _all the other users the grantee has granted the same permission to_, ->will also get their permission revoked. - -Valid permission names can be found in the article [DatabasePermissionSet Class properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.databasepermissionset#properties). - -## Requirements - -* Target machine must be running Windows Server 2012 or later. -* Target machine must be running SQL Server Database Engine 2012 or later. - -## 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+SqlDatabasePermission). From b88382c060228da6dfaf03ff63ee47f9d0f1933d Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 26 Jun 2022 13:45:39 +0200 Subject: [PATCH 04/98] Update files --- source/Classes/001.ResourceBase.ps1 | 22 ++++++++------ source/Classes/003.SqlDatabasePermission.ps1 | 32 +++++++++++--------- source/SqlServerDsc.psd1 | 31 ++++++++++--------- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/source/Classes/001.ResourceBase.ps1 b/source/Classes/001.ResourceBase.ps1 index 3d423ab96..3048a711b 100644 --- a/source/Classes/001.ResourceBase.ps1 +++ b/source/Classes/001.ResourceBase.ps1 @@ -36,20 +36,22 @@ class ResourceBase $getParameters = @{} + $specialKeyProperty = @() + # Set each key property to its value (property DnsServer is handled below). $keyProperty | Where-Object -FilterScript { - $_ -ne 'DnsServer' + $_ -notin $specialKeyProperty } | ForEach-Object -Process { $getParameters[$_] = $this.$_ } # Set ComputerName depending on value of DnsServer. - if ($this.DnsServer -ne 'localhost') - { - $getParameters['ComputerName'] = $this.DnsServer - } + # if ($this.DnsServer -ne 'localhost') + # { + # $getParameters['ComputerName'] = $this.DnsServer + # } $getCurrentStateResult = $this.GetCurrentState($getParameters) @@ -87,10 +89,10 @@ class ResourceBase Write-Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_, $this.GetType().Name) } - if ($this.DnsServer -ne 'localhost') - { - $propertiesToModify['ComputerName'] = $this.DnsServer - } + # if ($this.DnsServer -ne 'localhost') + # { + # $propertiesToModify['ComputerName'] = $this.DnsServer + # } <# Call the Modify() method with the properties that should be enforced @@ -187,7 +189,7 @@ class ResourceBase # This method should normally not be overridden. hidden [void] Assert() { - Assert-Module -ModuleName 'DnsServer' + #Assert-Module -ModuleName 'DnsServer' $this.AssertProperties() } diff --git a/source/Classes/003.SqlDatabasePermission.ps1 b/source/Classes/003.SqlDatabasePermission.ps1 index 354ae1f18..831b51d20 100644 --- a/source/Classes/003.SqlDatabasePermission.ps1 +++ b/source/Classes/003.SqlDatabasePermission.ps1 @@ -87,7 +87,9 @@ class SqlDatabasePermission : ResourceBase # Base method Get() call this method to get the current state as a CimInstance. [Microsoft.Management.Infrastructure.CimInstance] GetCurrentState([System.Collections.Hashtable] $properties) { - return (Get-DnsServerDsSetting @properties) + Write-Verbose -Message ($properties | Out-String) -Verbose + + return ConvertTo-CimInstance -Hashtable @{} #(Get-DnsServerDsSetting @properties) } [void] Set() @@ -102,7 +104,9 @@ class SqlDatabasePermission : ResourceBase #> [void] Modify([System.Collections.Hashtable] $properties) { - Set-DnsServerDsSetting @properties + Write-Verbose -Message ($properties | Out-String) -Verbose + + #Set-DnsServerDsSetting @properties } [System.Boolean] Test() @@ -113,17 +117,17 @@ class SqlDatabasePermission : ResourceBase hidden [void] AssertProperties() { - @( - 'DirectoryPartitionAutoEnlistInterval', - 'TombstoneInterval' - ) | ForEach-Object -Process { - $valueToConvert = $this.$_ - - # Only evaluate properties that have a value. - if ($null -ne $valueToConvert) - { - Assert-TimeSpan -PropertyName $_ -Value $valueToConvert -Minimum '0.00:00:00' - } - } + # @( + # 'DirectoryPartitionAutoEnlistInterval', + # 'TombstoneInterval' + # ) | ForEach-Object -Process { + # $valueToConvert = $this.$_ + + # # Only evaluate properties that have a value. + # if ($null -ne $valueToConvert) + # { + # Assert-TimeSpan -PropertyName $_ -Value $valueToConvert -Minimum '0.00:00:00' + # } + # } } } diff --git a/source/SqlServerDsc.psd1 b/source/SqlServerDsc.psd1 index bf33384fb..20f77d19d 100644 --- a/source/SqlServerDsc.psd1 +++ b/source/SqlServerDsc.psd1 @@ -1,46 +1,49 @@ @{ + # Script module or binary module file associated with this manifest. + RootModule = 'SqlServerDsc.psm1' + # Version number of this module. - moduleVersion = '0.0.1' + ModuleVersion = '0.0.1' # ID used to uniquely identify this module - GUID = '693ee082-ed36-45a7-b490-88b07c86b42f' + GUID = '693ee082-ed36-45a7-b490-88b07c86b42f' # Author of this module - Author = 'DSC Community' + Author = 'DSC Community' # Company or vendor of this module - CompanyName = 'DSC Community' + CompanyName = 'DSC Community' # Copyright statement for this module - Copyright = 'Copyright the DSC Community contributors. All rights reserved.' + Copyright = 'Copyright the DSC Community contributors. All rights reserved.' # Description of the functionality provided by this module - Description = 'Module with DSC resources for deployment and configuration of Microsoft SQL Server.' + Description = 'Module with DSC resources for deployment and configuration of Microsoft SQL Server.' # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '5.0' + PowerShellVersion = '5.0' # Minimum version of the common language runtime (CLR) required by this module - CLRVersion = '4.0' + CLRVersion = '4.0' # Functions to export from this module - FunctionsToExport = @() + FunctionsToExport = @() # Cmdlets to export from this module - CmdletsToExport = @() + CmdletsToExport = @() # Variables to export from this module - VariablesToExport = @() + VariablesToExport = @() # Aliases to export from this module - AliasesToExport = @() + AliasesToExport = @() DscResourcesToExport = @() - RequiredAssemblies = @() + RequiredAssemblies = @() # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. - PrivateData = @{ + PrivateData = @{ PSData = @{ # Set to a prerelease string value if the release should be a prerelease. From ed3d5d041e1f155b19b5990b52fad1dc26eedebe Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 27 Jun 2022 08:31:01 +0200 Subject: [PATCH 05/98] Add unit test for class resource --- .../Classes/SqlDatabasePermission.Tests.ps1 | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 new file mode 100644 index 000000000..be65db99f --- /dev/null +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -0,0 +1,98 @@ +<# + .SYNOPSIS + Unit test for DSC_SqlDatabasePermission DSC resource. +#> + +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + # $script:testEnvironment = Initialize-TestEnvironment ` + # -DSCModuleName $script:dscModuleName ` + # -DSCResourceName $script:dscResourceName ` + # -ResourceType 'Class' ` + # -TestType 'Unit' + + Import-Module -Name $script:dscModuleName + + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../TestHelpers/CommonTestHelper.psm1') + + # # Loading mocked classes + # Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') + + # Load the correct SQL Module stub + $script:stubModuleName = Import-SQLModuleStub -PassThru + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Restore-TestEnvironment -TestEnvironment $script:testEnvironment + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + # Unload the stub module. + Remove-SqlModuleStub -Name $script:stubModuleName + + # Remove module common test helper. + Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force +} + +Describe 'SqlDatabasePermission' { + Context 'When class is instantiated' { + It 'Should not throw an exception' { + InModuleScope -ScriptBlock { + { [SqlDatabasePermission]::new() } | Should -Not -Throw + } + } + + It 'SHould have a default or empty constructor' { + InModuleScope -ScriptBlock { + $instance = [SqlDatabasePermission]::new() + $instance | Should -Not -BeNullOrEmpty + } + } + + It 'Should be the correct type' { + InModuleScope -ScriptBlock { + $instance = [SqlDatabasePermission]::new() + $instance.GetType().Name | Should -Be 'SqlDatabasePermission' + } + } + } +} + +Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { +} From 259d7e656c6a46193b2b01f28b1758af01cca8c9 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 27 Jun 2022 16:55:39 +0200 Subject: [PATCH 06/98] Fix working resource --- build.yaml | 1 + source/Classes/001.ResourceBase.ps1 | 21 ++++++++-------- source/Classes/003.SqlDatabasePermission.ps1 | 25 +++++++++++++++---- source/en-US/SqlServerDsc.strings.psd1 | 9 +++++++ source/prefix.ps1 | 4 +++ .../Classes/SqlDatabasePermission.Tests.ps1 | 21 ++++++++++++++++ 6 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 source/en-US/SqlServerDsc.strings.psd1 create mode 100644 source/prefix.ps1 diff --git a/build.yaml b/build.yaml index aefbee919..d3d4d4be9 100644 --- a/build.yaml +++ b/build.yaml @@ -38,6 +38,7 @@ CopyPaths: - DSCResources - en-US - Modules +Prefix: prefix.ps1 Encoding: UTF8 VersionedOutputDirectory: true diff --git a/source/Classes/001.ResourceBase.ps1 b/source/Classes/001.ResourceBase.ps1 index 3048a711b..8864d7e3d 100644 --- a/source/Classes/001.ResourceBase.ps1 +++ b/source/Classes/001.ResourceBase.ps1 @@ -36,9 +36,16 @@ class ResourceBase $getParameters = @{} + # TODO: Should be a member, and for each property it should call back to the derived class for proper handling. $specialKeyProperty = @() - # Set each key property to its value (property DnsServer is handled below). + # Set ComputerName depending on value of DnsServer. + # if ($this.DnsServer -ne 'localhost') + # { + # $getParameters['ComputerName'] = $this.DnsServer + # } + + # Set each key property that does not need special handling (those were handle above). $keyProperty | Where-Object -FilterScript { $_ -notin $specialKeyProperty @@ -47,26 +54,20 @@ class ResourceBase $getParameters[$_] = $this.$_ } - # Set ComputerName depending on value of DnsServer. - # if ($this.DnsServer -ne 'localhost') - # { - # $getParameters['ComputerName'] = $this.DnsServer - # } - $getCurrentStateResult = $this.GetCurrentState($getParameters) $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) foreach ($propertyName in $this.PSObject.Properties.Name) { - if ($propertyName -in @($getCurrentStateResult.PSObject.Properties.Name)) + if ($propertyName -in @($getCurrentStateResult.Keys)) { $dscResourceObject.$propertyName = $getCurrentStateResult.$propertyName } } # Always set this as it won't be in the $getCurrentStateResult - $dscResourceObject.DnsServer = $this.DnsServer + #$dscResourceObject.DnsServer = $this.DnsServer # Return properties. return $dscResourceObject @@ -206,7 +207,7 @@ class ResourceBase } # This method must be overridden by a resource. - hidden [Microsoft.Management.Infrastructure.CimInstance] GetCurrentState([System.Collections.Hashtable] $properties) + hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { throw $this.localizedData.GetCurrentStateMethodNotImplemented } diff --git a/source/Classes/003.SqlDatabasePermission.ps1 b/source/Classes/003.SqlDatabasePermission.ps1 index 831b51d20..184915786 100644 --- a/source/Classes/003.SqlDatabasePermission.ps1 +++ b/source/Classes/003.SqlDatabasePermission.ps1 @@ -38,7 +38,10 @@ current computer name. .PARAMETER Permission - The database permission to enforce. + An array of database permissions to enforce. + + This is an array of CIM instances of class `DatabasePermission` from the + namespace `root/Microsoft/Windows/DesiredStateConfiguration`. .PARAMETER Ensure If the permission should be granted ('Present') or revoked ('Absent'). @@ -84,12 +87,24 @@ class SqlDatabasePermission : ResourceBase return ([ResourceBase] $this).Get() } - # Base method Get() call this method to get the current state as a CimInstance. - [Microsoft.Management.Infrastructure.CimInstance] GetCurrentState([System.Collections.Hashtable] $properties) + # Base method Get() call this method to get the current state as a hashtable. + hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { Write-Verbose -Message ($properties | Out-String) -Verbose - return ConvertTo-CimInstance -Hashtable @{} #(Get-DnsServerDsSetting @properties) + $currentEnsure = 'Absent' + + # TODO: Evaluate database permission current state + #(Get-DnsServerDsSetting @properties) + + $currentState = @{ + InstanceName = $properties.InstanceName + DatabaseName = $properties.DatabaseName + Name = $properties.Name + Ensure = $currentEnsure + } + + return $currentState } [void] Set() @@ -102,7 +117,7 @@ class SqlDatabasePermission : ResourceBase Base method Set() call this method with the properties that should be enforced and that are not in desired state. #> - [void] Modify([System.Collections.Hashtable] $properties) + hidden [void] Modify([System.Collections.Hashtable] $properties) { Write-Verbose -Message ($properties | Out-String) -Verbose diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 new file mode 100644 index 000000000..12f23e4ca --- /dev/null +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -0,0 +1,9 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource DnsServerDsc module. This file should only contain + localized strings for private and public functions. +#> + +ConvertFrom-StringData @' +'@ diff --git a/source/prefix.ps1 b/source/prefix.ps1 new file mode 100644 index 000000000..3e1799e82 --- /dev/null +++ b/source/prefix.ps1 @@ -0,0 +1,4 @@ +$script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules\DscResource.Common' +Import-Module -Name $script:dscResourceCommonModulePath + +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index be65db99f..2cb9f17c3 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -95,4 +95,25 @@ Describe 'SqlDatabasePermission' { } Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { + BeforeEach { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + } + } + } + + Context 'When the system is in the desired state' { + Context 'When the desired permission does exist' { + It 'Should return the state as absent' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.Get() + + $currentState.Ensure | Should -Be 'Absent' + } + } + } + } } From 9be88550be6abe0f7e429d6cba0aa3bf4b673f0d Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 27 Jun 2022 16:57:12 +0200 Subject: [PATCH 07/98] fix todo comment --- source/Classes/001.ResourceBase.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/source/Classes/001.ResourceBase.ps1 b/source/Classes/001.ResourceBase.ps1 index 8864d7e3d..16588aee4 100644 --- a/source/Classes/001.ResourceBase.ps1 +++ b/source/Classes/001.ResourceBase.ps1 @@ -17,6 +17,7 @@ class ResourceBase # Default constructor ResourceBase() { + # TODO: When this fails the LCM returns 'Failed to create an object of PowerShell class SqlDatabasePermission' instead of the actual error that occurred. $this.localizedData = Get-LocalizedDataRecursive -ClassName ($this | Get-ClassName -Recurse) } From e218d00f02b0779567c40a4d61a7c7e21f1b4881 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 28 Jun 2022 11:15:45 +0200 Subject: [PATCH 08/98] Fix unit tests for classes --- source/Classes/002.DatabasePermission.ps1 | 1 + .../Unit/Classes/DatabasePermission.Tests.ps1 | 76 ++ tests/Unit/Classes/Reason.Tests.ps1 | 70 ++ tests/Unit/Classes/ResourceBase.Tests.ps1 | 668 ++++++++++++++++++ .../Classes/SqlDatabasePermission.Tests.ps1 | 8 - 5 files changed, 815 insertions(+), 8 deletions(-) create mode 100644 tests/Unit/Classes/DatabasePermission.Tests.ps1 create mode 100644 tests/Unit/Classes/Reason.Tests.ps1 create mode 100644 tests/Unit/Classes/ResourceBase.Tests.ps1 diff --git a/source/Classes/002.DatabasePermission.ps1 b/source/Classes/002.DatabasePermission.ps1 index 5d22e6b4c..5e15bcb22 100644 --- a/source/Classes/002.DatabasePermission.ps1 +++ b/source/Classes/002.DatabasePermission.ps1 @@ -12,6 +12,7 @@ class DatabasePermission [DatabasePermissionState] $State + # TODO: Can we use a validate set for the permissions? [DscProperty(Mandatory)] [System.String] $Permission diff --git a/tests/Unit/Classes/DatabasePermission.Tests.ps1 b/tests/Unit/Classes/DatabasePermission.Tests.ps1 new file mode 100644 index 000000000..b68fa89c3 --- /dev/null +++ b/tests/Unit/Classes/DatabasePermission.Tests.ps1 @@ -0,0 +1,76 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'DatabasePermission' -Tag 'DatabasePermission' { + Context 'When instantiating the class' { + It 'Should not throw an error' { + $script:mockDatabasePermissionInstance = InModuleScope -ScriptBlock { + [DatabasePermission]::new() + } + } + + It 'Should be of the correct type' { + $mockDatabasePermissionInstance | Should -Not -BeNullOrEmpty + $mockDatabasePermissionInstance.GetType().Name | Should -Be 'DatabasePermission' + } + } + + Context 'When setting an reading values' { + It 'Should be able to set value in instance' { + $script:mockDatabasePermissionInstance = InModuleScope -ScriptBlock { + $databasPermissionInstance = [DatabasePermission]::new() + + $databasPermissionInstance.State = [DatabasePermissionState]::Grant + $databasPermissionInstance.Permission = 'select' + + return $databasPermissionInstance + } + } + + It 'Should be able read the values from instance' { + $mockDatabasePermissionInstance.State | Should -Be 'Grant' + $mockDatabasePermissionInstance.Permission = 'select' + } + } +} diff --git a/tests/Unit/Classes/Reason.Tests.ps1 b/tests/Unit/Classes/Reason.Tests.ps1 new file mode 100644 index 000000000..c7676d7b2 --- /dev/null +++ b/tests/Unit/Classes/Reason.Tests.ps1 @@ -0,0 +1,70 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Reason' -Tag 'Reason' { + Context 'When instantiating the class' { + It 'Should not throw an error' { + $script:mockReasonInstance = InModuleScope -ScriptBlock { + [Reason]::new() + } + } + + It 'Should be of the correct type' { + $mockReasonInstance | Should -Not -BeNullOrEmpty + $mockReasonInstance.GetType().Name | Should -Be 'Reason' + } + } + + Context 'When setting an reading values' { + It 'Should be able to set value in instance' { + $mockReasonInstance.Code = '{0}:{0}:Ensure' -f $mockReasonInstance.GetType() + $mockReasonInstance.Phrase = 'It should be absent, but it was present.' + } + + It 'Should be able read the values from instance' { + $mockReasonInstance.Code | Should -Be ('{0}:{0}:Ensure' -f 'Reason') + $mockReasonInstance.Phrase = 'It should be absent, but it was present.' + } + } +} diff --git a/tests/Unit/Classes/ResourceBase.Tests.ps1 b/tests/Unit/Classes/ResourceBase.Tests.ps1 new file mode 100644 index 000000000..a75d0601d --- /dev/null +++ b/tests/Unit/Classes/ResourceBase.Tests.ps1 @@ -0,0 +1,668 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'ResourceBase\GetCurrentState()' -Tag 'GetCurrentState' { + Context 'When the required methods are not overridden' { + BeforeAll { + $mockResourceBaseInstance = InModuleScope -ScriptBlock { + [ResourceBase]::new() + } + } + + Context 'When there is no override for the method GetCurrentState' { + It 'Should throw the correct error' { + { $mockResourceBaseInstance.GetCurrentState(@{}) } | Should -Throw $mockResourceBaseInstance.GetCurrentStateMethodNotImplemented + } + } + } +} + +Describe 'ResourceBase\Modify()' -Tag 'Modify' { + Context 'When the required methods are not overridden' { + BeforeAll { + $mockResourceBaseInstance = InModuleScope -ScriptBlock { + [ResourceBase]::new() + } + } + + + Context 'When there is no override for the method Modify' { + It 'Should throw the correct error' { + { $mockResourceBaseInstance.Modify(@{}) } | Should -Throw $mockResourceBaseInstance.ModifyMethodNotImplemented + } + } + } +} + +Describe 'ResourceBase\AssertProperties()' -Tag 'AssertProperties' { + BeforeAll { + $mockResourceBaseInstance = InModuleScope -ScriptBlock { + [ResourceBase]::new() + } + } + + + It 'Should not throw' { + { $mockResourceBaseInstance.AssertProperties() } | Should -Not -Throw + } +} + +Describe 'ResourceBase\Get()' -Tag 'Get' { + Context 'When the system is in the desired state' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it outside the here-string PowerShell + will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = 'MyValue2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + } + } + } +} + +Describe 'ResourceBase\Test()' -Tag 'Test' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + <# + This will override (mock) the method Compare() that is called by Test(). + Overriding this method is something a derived class normally should not + do, but done here to simplify the tests. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [System.Collections.Hashtable[]] Compare() + { + return $null + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Test() | Should -BeTrue + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + <# + This will override (mock) the method Compare() that is called by Test(). + Overriding this method is something a derived class normally should not + do, but done here to simplify the tests. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [System.Collections.Hashtable[]] Compare() + { + # Could just return any non-null object, but mocking a real result. + return @{ + Property = 'MyResourceProperty2' + ExpectedValue = '1' + ActualValue = '2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Test() | Should -BeFalse + } + } + } +} + +Describe 'ResourceBase\Compare()' -Tag 'Compare' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty + + [ResourceBase] Get() + { + # Creates a new instance of the mock instance MyMockResource. + $currentStateInstance = [System.Activator]::CreateInstance($this.GetType()) + + $currentStateInstance.MyResourceProperty2 = 'MyValue1' + $currentStateInstance.MyResourceReadProperty = 'MyReadValue1' + + return $currentStateInstance + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + Context 'When no properties are enforced' { + It 'Should not return any property to enforce' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Compare() | Should -BeNullOrEmpty + } + } + } + + Context 'When one property are enforced but in desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceProperty2 = 'MyValue1' + } + } + + It 'Should not return any property to enforce' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Compare() | Should -BeNullOrEmpty -Because 'no result ($null) means all properties are in desired state' + } + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty()] + [System.String] + $MyResourceProperty3 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty + + [ResourceBase] Get() + { + # Creates a new instance of the mock instance MyMockResource. + $currentStateInstance = [System.Activator]::CreateInstance($this.GetType()) + + $currentStateInstance.MyResourceProperty2 = 'MyValue1' + $currentStateInstance.MyResourceProperty3 = 'MyValue2' + $currentStateInstance.MyResourceReadProperty = 'MyReadValue1' + + return $currentStateInstance + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + Context 'When only enforcing one property' { + BeforeAll { + InModuleScope -ScriptBlock { + # Set desired value for the property that should be enforced. + $mockResourceBaseInstance.MyResourceProperty2 = 'MyNewValue' + } + } + + It 'Should return the correct property that is not in desired state' { + InModuleScope -ScriptBlock { + $compareResult = $mockResourceBaseInstance.Compare() + $compareResult | Should -HaveCount 1 + + $compareResult[0].Property | Should -Be 'MyResourceProperty2' + $compareResult[0].ExpectedValue | Should -Be 'MyNewValue' + $compareResult[0].ActualValue | Should -Be 'MyValue1' + } + } + } + + Context 'When only enforcing two properties' { + BeforeAll { + InModuleScope -ScriptBlock { + # Set desired value for the properties that should be enforced. + $mockResourceBaseInstance.MyResourceProperty2 = 'MyNewValue1' + $mockResourceBaseInstance.MyResourceProperty3 = 'MyNewValue2' + } + } + + It 'Should return the correct property that is not in desired state' { + InModuleScope -ScriptBlock { + <# + The properties that are returned are not [ordered] so they can + come in any order from run to run. The test handle that. + #> + $compareResult = $mockResourceBaseInstance.Compare() + $compareResult | Should -HaveCount 2 + + $compareResult.Property | Should -Contain 'MyResourceProperty2' + $compareResult.Property | Should -Contain 'MyResourceProperty3' + + $compareProperty = $compareResult.Where( { $_.Property -eq 'MyResourceProperty2' }) + $compareProperty.ExpectedValue | Should -Be 'MyNewValue1' + $compareProperty.ActualValue | Should -Be 'MyValue1' + + $compareProperty = $compareResult.Where( { $_.Property -eq 'MyResourceProperty3' }) + $compareProperty.ExpectedValue | Should -Be 'MyNewValue2' + $compareProperty.ActualValue | Should -Be 'MyValue2' + } + } + } + } +} + +Describe 'ResourceBase\GetDesiredStateForSplatting()' -Tag 'GetDesiredStateForSplatting' { + BeforeAll { + $mockResourceBaseInstance = InModuleScope -ScriptBlock { + [ResourceBase]::new() + } + + $mockProperties = @( + @{ + Property = 'MyResourceProperty1' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + }, + @{ + Property = 'MyResourceProperty2' + ExpectedValue = 'MyNewValue2' + ActualValue = 'MyValue2' + } + ) + } + + It 'Should return the correct values in a hashtable' { + $getDesiredStateForSplattingResult = $mockResourceBaseInstance.GetDesiredStateForSplatting($mockProperties) + + $getDesiredStateForSplattingResult | Should -BeOfType [System.Collections.Hashtable] + + $getDesiredStateForSplattingResult.Keys | Should -HaveCount 2 + $getDesiredStateForSplattingResult.Keys | Should -Contain 'MyResourceProperty1' + $getDesiredStateForSplattingResult.Keys | Should -Contain 'MyResourceProperty2' + + $getDesiredStateForSplattingResult.MyResourceProperty1 | Should -Be 'MyNewValue1' + $getDesiredStateForSplattingResult.MyResourceProperty2 | Should -Be 'MyNewValue2' + } +} + +Describe 'ResourceBase\Set()' -Tag 'Set' { + BeforeAll { + Mock -CommandName Assert-Module + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty()] + [System.String] + $MyResourceProperty3 + + # Hidden property to determine whether the method Modify() was called. + hidden [System.Collections.Hashtable] $mockModifyProperties = @{} + + [System.Collections.Hashtable[]] Compare() + { + return $null + } + + [void] Modify([System.Collections.Hashtable] $properties) + { + $this.mockModifyProperties = $properties + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should not set any property' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Set() + + $mockResourceBaseInstance.mockModifyProperties | Should -BeNullOrEmpty + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When setting one property' { + BeforeAll { + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty()] + [System.String] + $MyResourceProperty3 + + # Hidden property to determine whether the method Modify() was called. + hidden [System.Collections.Hashtable] $mockModifyProperties = @{} + + [System.Collections.Hashtable[]] Compare() + { + return @( + @{ + Property = 'MyResourceProperty2' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + } + ) + } + + [void] Modify([System.Collections.Hashtable] $properties) + { + $this.mockModifyProperties = $properties + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should set the correct property' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Set() + + $mockResourceBaseInstance.mockModifyProperties.Keys | Should -HaveCount 1 + $mockResourceBaseInstance.mockModifyProperties.Keys | Should -Contain 'MyResourceProperty2' + + $mockResourceBaseInstance.mockModifyProperties.MyResourceProperty2 | Should -Contain 'MyNewValue1' + } + } + } + + Context 'When setting two properties' { + BeforeAll { + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty()] + [System.String] + $MyResourceProperty3 + + # Hidden property to determine whether the method Modify() was called. + hidden [System.Collections.Hashtable] $mockModifyProperties = @{} + + [System.Collections.Hashtable[]] Compare() + { + return @( + @{ + Property = 'MyResourceProperty2' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + }, + @{ + Property = 'MyResourceProperty3' + ExpectedValue = 'MyNewValue2' + ActualValue = 'MyValue2' + } + ) + } + + [void] Modify([System.Collections.Hashtable] $properties) + { + $this.mockModifyProperties = $properties + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should set the correct properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Set() + + $mockResourceBaseInstance.mockModifyProperties.Keys | Should -HaveCount 2 + $mockResourceBaseInstance.mockModifyProperties.Keys | Should -Contain 'MyResourceProperty2' + $mockResourceBaseInstance.mockModifyProperties.Keys | Should -Contain 'MyResourceProperty3' + + $mockResourceBaseInstance.mockModifyProperties.MyResourceProperty2 | Should -Contain 'MyNewValue1' + $mockResourceBaseInstance.mockModifyProperties.MyResourceProperty3 | Should -Contain 'MyNewValue2' + } + } + } + } +} diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index 2cb9f17c3..171d26c67 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -32,12 +32,6 @@ BeforeDiscovery { BeforeAll { $script:dscModuleName = 'SqlServerDsc' - # $script:testEnvironment = Initialize-TestEnvironment ` - # -DSCModuleName $script:dscModuleName ` - # -DSCResourceName $script:dscResourceName ` - # -ResourceType 'Class' ` - # -TestType 'Unit' - Import-Module -Name $script:dscModuleName Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../TestHelpers/CommonTestHelper.psm1') @@ -58,8 +52,6 @@ AfterAll { $PSDefaultParameterValues.Remove('Mock:ModuleName') $PSDefaultParameterValues.Remove('Should:ModuleName') - # Restore-TestEnvironment -TestEnvironment $script:testEnvironment - # Unload the module being tested so that it doesn't impact any other tests. Get-Module -Name $script:dscModuleName -All | Remove-Module -Force From 76757b42d90d8a37e62b09075cf9847fd6866131 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 29 Jun 2022 18:03:43 +0200 Subject: [PATCH 09/98] Add public functions --- coverage.xml | 248 ++++++++++++++++++ source/Classes/001.ResourceBase.ps1 | 107 ++++---- source/Classes/003.SqlDatabasePermission.ps1 | 37 +-- source/Private/Get-DesiredStateProperty.ps1 | 44 ++++ source/Private/Get-KeyProperty.ps1 | 42 +++ .../Public/Connect-SqlDscDatabaseEngine.ps1 | 81 ++++++ .../Public/Get-SqlDscDatabasePermission.ps1 | 120 +++++++++ .../Public/Test-SqlDscIsDatabasePrincipal.ps1 | 84 ++++++ source/en-US/ResourceBase.strings.psd1 | 12 +- .../en-US/SqlDatabasePermission.strings.psd1 | 19 +- source/prefix.ps1 | 6 +- tests/Unit/Classes/ResourceBase.Tests.ps1 | 6 +- tests/Unit/Private/Get-ClassName.Tests.ps1 | 109 ++++++++ .../Get-DesiredStateProperty.Tests.ps1 | 99 +++++++ tests/Unit/Private/Get-KeyProperty.Tests.ps1 | 100 +++++++ .../Get-LocalizedDataRecursive.Tests.ps1 | 161 ++++++++++++ 16 files changed, 1182 insertions(+), 93 deletions(-) create mode 100644 coverage.xml create mode 100644 source/Private/Get-DesiredStateProperty.ps1 create mode 100644 source/Private/Get-KeyProperty.ps1 create mode 100644 source/Public/Connect-SqlDscDatabaseEngine.ps1 create mode 100644 source/Public/Get-SqlDscDatabasePermission.ps1 create mode 100644 source/Public/Test-SqlDscIsDatabasePrincipal.ps1 create mode 100644 tests/Unit/Private/Get-ClassName.Tests.ps1 create mode 100644 tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 create mode 100644 tests/Unit/Private/Get-KeyProperty.Tests.ps1 create mode 100644 tests/Unit/Private/Get-LocalizedDataRecursive.Tests.ps1 diff --git a/coverage.xml b/coverage.xml new file mode 100644 index 000000000..1166067ec --- /dev/null +++ b/coverage.xml @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/Classes/001.ResourceBase.ps1 b/source/Classes/001.ResourceBase.ps1 index 16588aee4..015cd34d1 100644 --- a/source/Classes/001.ResourceBase.ps1 +++ b/source/Classes/001.ResourceBase.ps1 @@ -25,37 +25,19 @@ class ResourceBase { $this.Assert() - Write-Verbose -Message ($this.localizedData.GetCurrentState -f $this.DnsServer, $this.GetType().Name) - # Get all key properties. - $keyProperty = $this | - Get-Member -MemberType 'Property' | - Select-Object -ExpandProperty Name | - Where-Object -FilterScript { - $this.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'Key' }).NamedArguments.TypedValue.Value -eq $true - } - - $getParameters = @{} - - # TODO: Should be a member, and for each property it should call back to the derived class for proper handling. - $specialKeyProperty = @() + $keyProperty = $this | Get-KeyProperty - # Set ComputerName depending on value of DnsServer. - # if ($this.DnsServer -ne 'localhost') - # { - # $getParameters['ComputerName'] = $this.DnsServer - # } + Write-Verbose -Verbose -Message ($this.localizedData.GetCurrentState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) - # Set each key property that does not need special handling (those were handle above). - $keyProperty | - Where-Object -FilterScript { - $_ -notin $specialKeyProperty - } | - ForEach-Object -Process { - $getParameters[$_] = $this.$_ - } + <# + TODO: Should call back to the derived class for proper handling of adding + additional parameters to the variable $keyProperty that needs to be + passed to GetCurrentState(). + #> + #$specialKeyProperty = @() - $getCurrentStateResult = $this.GetCurrentState($getParameters) + $getCurrentStateResult = $this.GetCurrentState($keyProperty) $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) @@ -67,18 +49,18 @@ class ResourceBase } } - # Always set this as it won't be in the $getCurrentStateResult - #$dscResourceObject.DnsServer = $this.DnsServer - # Return properties. return $dscResourceObject } [void] Set() { - $this.Assert() + # Get all key properties. + $keyProperty = $this | Get-KeyProperty - Write-Verbose -Message ($this.localizedData.SetDesiredState -f $this.DnsServer, $this.GetType().Name) + Write-Verbose -Verbose -Message ($this.localizedData.SetDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + + $this.Assert() # Call the Compare method to get enforced properties that are not in desired state. $propertiesNotInDesiredState = $this.Compare() @@ -88,14 +70,9 @@ class ResourceBase $propertiesToModify = $this.GetDesiredStateForSplatting($propertiesNotInDesiredState) $propertiesToModify.Keys | ForEach-Object -Process { - Write-Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_, $this.GetType().Name) + Write-Verbose -Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_) } - # if ($this.DnsServer -ne 'localhost') - # { - # $propertiesToModify['ComputerName'] = $this.DnsServer - # } - <# Call the Modify() method with the properties that should be enforced and was not in desired state. @@ -104,13 +81,16 @@ class ResourceBase } else { - Write-Verbose -Message $this.localizedData.NoPropertiesToSet + Write-Verbose -Verbose -Message $this.localizedData.NoPropertiesToSet } } [System.Boolean] Test() { - Write-Verbose -Message ($this.localizedData.TestDesiredState -f $this.DnsServer, $this.GetType().Name) + # Get all key properties. + $keyProperty = $this | Get-KeyProperty + + Write-Verbose -Verbose -Message ($this.localizedData.TestDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) $this.Assert() @@ -129,37 +109,26 @@ class ResourceBase if ($isInDesiredState) { - Write-Verbose -Message ($this.localizedData.InDesiredState -f $this.DnsServer, $this.GetType().Name) + Write-Verbose -Verbose -Message $this.localizedData.InDesiredState } else { - Write-Verbose -Message ($this.localizedData.NotInDesiredState -f $this.DnsServer, $this.GetType().Name) + Write-Verbose -Verbose -Message $this.localizedData.NotInDesiredState } return $isInDesiredState } <# - Returns a hashtable containing all properties that should be enforced. + Returns a hashtable containing all properties that should be enforced and + are not in desired state. + This method should normally not be overridden. #> hidden [System.Collections.Hashtable[]] Compare() { $currentState = $this.Get() | ConvertFrom-DscResourceInstance - $desiredState = $this | ConvertFrom-DscResourceInstance - - <# - Remove properties that have $null as the value, and remove read - properties so that there is no chance to compare those. - #> - @($desiredState.Keys) | ForEach-Object -Process { - $isReadProperty = $this.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'NotConfigurable' }).NamedArguments.TypedValue.Value -eq $true - - if ($isReadProperty -or $null -eq $desiredState[$_]) - { - $desiredState.Remove($_) - } - } + $desiredState = $this | Get-DesiredStateProperty $CompareDscParameterState = @{ CurrentValues = $currentState @@ -191,23 +160,35 @@ class ResourceBase # This method should normally not be overridden. hidden [void] Assert() { - #Assert-Module -ModuleName 'DnsServer' + $desiredState = $this | Get-DesiredStateProperty - $this.AssertProperties() + $this.AssertProperties($desiredState) } - # This method can be overridden if resource specific asserts are needed. - hidden [void] AssertProperties() + <# + This method can be overridden if resource specific property asserts are + needed. The parameter properties will contain the properties that was + passed a value. + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidEmptyNamedBlocks', '')] + hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { } - # This method must be overridden by a resource. + <# + This method must be overridden by a resource. The parameter properties will + contain the properties that should be enforced and that are not in desired + state. + #> hidden [void] Modify([System.Collections.Hashtable] $properties) { throw $this.localizedData.ModifyMethodNotImplemented } - # This method must be overridden by a resource. + <# + This method must be overridden by a resource. The parameter properties will + contain the key properties. + #> hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { throw $this.localizedData.GetCurrentStateMethodNotImplemented diff --git a/source/Classes/003.SqlDatabasePermission.ps1 b/source/Classes/003.SqlDatabasePermission.ps1 index 184915786..5b894cbe8 100644 --- a/source/Classes/003.SqlDatabasePermission.ps1 +++ b/source/Classes/003.SqlDatabasePermission.ps1 @@ -87,11 +87,24 @@ class SqlDatabasePermission : ResourceBase return ([ResourceBase] $this).Get() } - # Base method Get() call this method to get the current state as a hashtable. - hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + [System.Boolean] Test() { - Write-Verbose -Message ($properties | Out-String) -Verbose + # Call the base method to test all of the properties that should be enforced. + return ([ResourceBase] $this).Test() + } + [void] Set() + { + # Call the base method to enforce the properties. + ([ResourceBase] $this).Set() + } + + <# + Base method Get() call this method to get the current state as a hashtable. + The parameter properties will contain the key properties. + #> + hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { $currentEnsure = 'Absent' # TODO: Evaluate database permission current state @@ -107,12 +120,6 @@ class SqlDatabasePermission : ResourceBase return $currentState } - [void] Set() - { - # Call the base method to enforce the properties. - ([ResourceBase] $this).Set() - } - <# Base method Set() call this method with the properties that should be enforced and that are not in desired state. @@ -124,13 +131,11 @@ class SqlDatabasePermission : ResourceBase #Set-DnsServerDsSetting @properties } - [System.Boolean] Test() - { - # Call the base method to test all of the properties that should be enforced. - return ([ResourceBase] $this).Test() - } - - hidden [void] AssertProperties() + <# + Base method Assert() call this method with the properties that was passed + a value. + #> + hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { # @( # 'DirectoryPartitionAutoEnlistInterval', diff --git a/source/Private/Get-DesiredStateProperty.ps1 b/source/Private/Get-DesiredStateProperty.ps1 new file mode 100644 index 000000000..ab7a0f431 --- /dev/null +++ b/source/Private/Get-DesiredStateProperty.ps1 @@ -0,0 +1,44 @@ + +<# + .SYNOPSIS + Returns the properties that should be enforced for the desired state. + + .DESCRIPTION + Returns the properties that should be enforced for the desired state. + This function converts a PSObject into a hashtable containing the properties + that should be enforced. + + .PARAMETER InputObject + The object that contain the properties with the desired state. + + .OUTPUTS + Hashtable +#> +function Get-DesiredStateProperty +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject + ) + + $desiredStateProperty = $InputObject | ConvertFrom-DscResourceInstance + + <# + Remove properties that have $null as the value, and remove read + properties so that there is no chance to compare those. + #> + @($desiredStateProperty.Keys) | ForEach-Object -Process { + $isReadProperty = $InputObject.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'NotConfigurable' }).NamedArguments.TypedValue.Value -eq $true + + if ($isReadProperty -or $null -eq $desiredStateProperty[$_]) + { + $desiredStateProperty.Remove($_) + } + } + + return $desiredStateProperty +} diff --git a/source/Private/Get-KeyProperty.ps1 b/source/Private/Get-KeyProperty.ps1 new file mode 100644 index 000000000..69d50f7ff --- /dev/null +++ b/source/Private/Get-KeyProperty.ps1 @@ -0,0 +1,42 @@ + +<# + .SYNOPSIS + Returns the DSC resource key property and its value. + + .DESCRIPTION + Returns the DSC resource key property and its value. + + .PARAMETER InputObject + The object that contain the key property. + + .OUTPUTS + Hashtable +#> +function Get-KeyProperty +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject + ) + + # Get all key properties. + $keyProperty = $InputObject | + Get-Member -MemberType 'Property' | + Select-Object -ExpandProperty 'Name' | + Where-Object -FilterScript { + $InputObject.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'Key' }).NamedArguments.TypedValue.Value -eq $true + } + + # Return a hashtable containing each key property and its value. + $getKeyPropertyResult = @{} + + $keyProperty | ForEach-Object -Process { + $getKeyPropertyResult.$_ = $InputObject.$_ + } + + return $getKeyPropertyResult +} diff --git a/source/Public/Connect-SqlDscDatabaseEngine.ps1 b/source/Public/Connect-SqlDscDatabaseEngine.ps1 new file mode 100644 index 000000000..ddccc99af --- /dev/null +++ b/source/Public/Connect-SqlDscDatabaseEngine.ps1 @@ -0,0 +1,81 @@ +<# + .SYNOPSIS + Connect to a SQL Server Database Engine and return the server object. + + .PARAMETER ServerName + String containing the host name of the SQL Server to connect to. + Default value is the current computer name. + + .PARAMETER InstanceName + String containing the SQL Server Database Engine instance to connect to. + Default value is 'MSSQLSERVER'. + + .PARAMETER Credential + The credentials to use to impersonate a user when connecting to the + SQL Server Database Engine instance. If this parameter is left out, then + the current user will be used to connect to the SQL Server Database Engine + instance using Windows Integrated authentication. + + .PARAMETER LoginType + Specifies which type of logon credential should be used. The valid types + are 'WindowsUser' or 'SqlLogin'. Default value is 'WindowsUser' + If set to 'WindowsUser' then the it will impersonate using the Windows + login specified in the parameter Credential. + If set to 'WindowsUser' then the it will impersonate using the native SQL + login specified in the parameter Credential. + + .PARAMETER StatementTimeout + Set the query StatementTimeout in seconds. Default 600 seconds (10 minutes). + + .EXAMPLE + Connect-SqlDscDatabaseEngine + + Connects to the default instance on the local server. + + .EXAMPLE + Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + + Connects to the instance 'MyInstance' on the local server. + + .EXAMPLE + Connect-SqlDscDatabaseEngine -ServerName 'sql.company.local' -InstanceName 'MyInstance' + + Connects to the instance 'MyInstance' on the server 'sql.company.local'. +#> +function Connect-SqlDscDatabaseEngine +{ + [CmdletBinding(DefaultParameterSetName = 'SqlServer')] + param + ( + [Parameter(ParameterSetName = 'SqlServer')] + [Parameter(ParameterSetName = 'SqlServerWithCredential')] + [ValidateNotNull()] + [System.String] + $ServerName = (Get-ComputerName), + + [Parameter(ParameterSetName = 'SqlServer')] + [Parameter(ParameterSetName = 'SqlServerWithCredential')] + [ValidateNotNull()] + [System.String] + $InstanceName = 'MSSQLSERVER', + + [Parameter(ParameterSetName = 'SqlServerWithCredential', Mandatory = $true)] + [ValidateNotNull()] + [Alias('SetupCredential', 'DatabaseCredential')] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter(ParameterSetName = 'SqlServerWithCredential')] + [ValidateSet('WindowsUser', 'SqlLogin')] + [System.String] + $LoginType = 'WindowsUser', + + [Parameter()] + [ValidateNotNull()] + [System.Int32] + $StatementTimeout = 600 + ) + + # Call the private function. + return (Connect-Sql @PSBoundParameters) +} diff --git a/source/Public/Get-SqlDscDatabasePermission.ps1 b/source/Public/Get-SqlDscDatabasePermission.ps1 new file mode 100644 index 000000000..5448c511d --- /dev/null +++ b/source/Public/Get-SqlDscDatabasePermission.ps1 @@ -0,0 +1,120 @@ +<# + .SYNOPSIS + Returns the current permissions for the database principal. + + .PARAMETER ServerObject + Specifies current server connection object. + + # .PARAMETER InstanceName + # Specifies the SQL instance for the database. + + .PARAMETER DatabaseName + Specifies the SQL database name. + + .PARAMETER Name + Specifies the name of the database principal for which the permission set is returned. + + # .PARAMETER PermissionState + # This is the state of permission set. Valid values are 'Grant' or 'Deny'. + + # .PARAMETER Permissions + # This is a list that represents a SQL Server set of database permissions. + + .NOTES + This command excludes fixed roles like db_datareader. + + TODO: This function will not throw an error if for example the database + does not exist, so that the Get() method of the resource does not + throw. Suggest adding optional parmeter 'FailOnError', + or 'EvaluateMandatoryProperties', or a combination. +#> +function Get-SqlDscDatabasePermission +{ + [CmdletBinding()] + [OutputType([System.String[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + # [Parameter(Mandatory = $true)] + # [System.String] + # $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $DatabaseName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + + # [Parameter(Mandatory = $true)] + # [ValidateSet('Grant', 'Deny', 'GrantWithGrant')] + # [System.String] + # $PermissionState, + + # [Parameter(Mandatory = $true)] + # [System.String[]] + # $Permissions, + + # [Parameter()] + # [ValidateNotNullOrEmpty()] + # [System.String] + # $ServerName = (Get-ComputerName) + ) + + # Initialize variable permission + [System.String[]] $getSqlDatabasePermissionResult = @() + + $sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName] + + if ($sqlDatabaseObject) + { + $isDatabasePrincipal = Test-SqlDscIsDatabasePrincipal @PSBoundParameters -ExcludeFixedRoles + + if ($isDatabasePrincipal) + { + $databasePermissionInfo = $sqlDatabaseObject.EnumDatabasePermissions($Name) | + Where-Object -FilterScript { + $_.PermissionState -eq $PermissionState + } + + if ($databasePermissionInfo) + { + foreach ($currentDatabasePermissionInfo in $databasePermissionInfo) + { + $permissionProperty = ( + $currentDatabasePermissionInfo.PermissionType | + Get-Member -MemberType Property + ).Name + + foreach ($currentPermissionProperty in $permissionProperty) + { + if ($currentDatabasePermissionInfo.PermissionType."$currentPermissionProperty") + { + $getSqlDatabasePermissionResult += $currentPermissionProperty + } + } + + # Remove any duplicate permissions. + $getSqlDatabasePermissionResult = @( + $getSqlDatabasePermissionResult | + Sort-Object -Unique + ) + } + } + } + else + { + Write-Verbose -Message ("The database principal '{0}' is neither a user, database role (user-defined), or database application role in the database '{1}'. (GETSDP0001)." -f $Name, $DatabaseName) + } + } + else + { + Write-Verbose -Message ("The database '{0}' did not exist. (GETSDP0002)" -f $DatabaseName) + } + + return [System.String[]] $getSqlDatabasePermissionResult +} diff --git a/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 b/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 new file mode 100644 index 000000000..9b28a354a --- /dev/null +++ b/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 @@ -0,0 +1,84 @@ +<# + .SYNOPSIS + Returns whether the database principal exist. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER DatabaseName + Specifies the SQL database name. + + .PARAMETER Name + Specifies the name of the database principal. +#> +function Test-SqlDscIsDatabasePrincipal +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $DatabaseName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ExcludeUsers, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ExcludeRoles, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ExcludeFixedRoles, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ExcludeApplicationRoles + ) + + $principalExist = $false + + $sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName] + + if (-not $ExcludeUsers.IsPresent -and $sqlDatabaseObject.Users[$Name]) + { + $principalExist = $true + } + + if (-not $ExcludeRoles.IsPresent) + { + $userDefinedRole = if ($ExcludeFixedRoles.IsPresent) + { + # Skip fixed roles like db_datareader. + $sqlDatabaseObject.Roles | Where-Object -FilterScript { + -not $_.IsFixedRole -and $_.Name -eq $Name + } + } + else + { + $sqlDatabaseObject.Roles[$Name] + } + + if ($userDefinedRole) + { + $principalExist = $true + } + } + + if (-not $ExcludeApplicationRoles.IsPresent -and $sqlDatabaseObject.ApplicationRoles[$Name]) + { + $principalExist = $true + } + + return $principalExist +} diff --git a/source/en-US/ResourceBase.strings.psd1 b/source/en-US/ResourceBase.strings.psd1 index f73e82d47..05f4ff47a 100644 --- a/source/en-US/ResourceBase.strings.psd1 +++ b/source/en-US/ResourceBase.strings.psd1 @@ -5,12 +5,12 @@ #> ConvertFrom-StringData @' - GetCurrentState = Getting the current state of {1} for the server '{0}'. (RB0001) - TestDesiredState = Determining the current state of {1} for the server '{0}'. (RB0002) - SetDesiredState = Setting the desired state of {1} for the server '{0}'. (RB0003) - NotInDesiredState = The {1} for the server '{0}' is not in desired state. (RB0004) - InDesiredState = The {1} for the server '{0}' is in desired state. (RB0005) - SetProperty = The {2} property '{0}' will be set to '{1}'. (RB0006) + GetCurrentState = Getting the current state for resource '{0}' using the key property '{1}'. (RB0001) + TestDesiredState = Determining the current state for resource '{0}' using the key property '{1}'. (RB0002) + SetDesiredState = Setting the desired state for resource '{0}' using the key property '{1}'. (RB0003) + NotInDesiredState = The current state is not the desired state. (RB0004) + InDesiredState = The current state is the desired state. (RB0005) + SetProperty = The property '{0}' will be set to '{1}'. (RB0006) NoPropertiesToSet = All properties are in desired state. (RB0007) ModifyMethodNotImplemented = An override for the method Modify() is not implemented in the resource. (RB0008) GetCurrentStateMethodNotImplemented = An override for the method GetCurrentState() is not implemented in the resource. (RB0009) diff --git a/source/en-US/SqlDatabasePermission.strings.psd1 b/source/en-US/SqlDatabasePermission.strings.psd1 index 69ceec5e3..af9d7a5b9 100644 --- a/source/en-US/SqlDatabasePermission.strings.psd1 +++ b/source/en-US/SqlDatabasePermission.strings.psd1 @@ -5,10 +5,17 @@ #> ConvertFrom-StringData @' - GetCurrentState = Getting the current state of the database permssions for the user '{0}'. (SDP0001) - TestDesiredState = Determining the current state of the database permssions for the user '{0}'. (SDP0002) - SetDesiredState = Setting the desired state for the database permssions for the user '{0}'. (SDP0003) - NotInDesiredState = The database permssions for the user '{0}' is not in desired state. (SDP0004) - InDesiredState = The database permssions for the user '{0}' is in desired state. (SDP0005) - SetProperty = The permission '{0}' will be set to '{1}'. (SDP0006) + # Strings overrides for the ResourceBase's default strings. + # None + + # Strings directly used by the derived class SqlDatabasePermission. + + GetDatabasePermission = Get permissions for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0001) + DatabaseNotFound = The database '{0}' does not exist. (SDP0002) + ChangePermissionForUser = Changing the permission for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0003) + NameIsMissing = The name '{0}' is neither a database user, database role (user-defined), or database application role in the database '{1}'. (SDP0004) + AddPermission = {0} the permissions '{1}' to the database '{2}'. (SDP0005) + DropPermission = Revoking the {0} permissions '{1}' from the database '{2}'. (SDP0006) + FailedToSetPermissionDatabase = Failed to set the permissions for the login '{0}' in the database '{1}'. (SDP0007) + TestingConfiguration = Determines if the user '{0}' has the correct permissions in the database '{1}' on the instance '{2}'. (SDP0008) '@ diff --git a/source/prefix.ps1 b/source/prefix.ps1 index 3e1799e82..34b428632 100644 --- a/source/prefix.ps1 +++ b/source/prefix.ps1 @@ -1,4 +1,8 @@ -$script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules\DscResource.Common' +$script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/DscResource.Common' Import-Module -Name $script:dscResourceCommonModulePath +# TODO: The goal would be to remove this, when no classes and public or private functions need it. +$script:sqlServerDscCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/SqlServerDsc.Common' +Import-Module -Name $script:sqlServerDscCommonModulePath + $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' diff --git a/tests/Unit/Classes/ResourceBase.Tests.ps1 b/tests/Unit/Classes/ResourceBase.Tests.ps1 index a75d0601d..58e0f497e 100644 --- a/tests/Unit/Classes/ResourceBase.Tests.ps1 +++ b/tests/Unit/Classes/ResourceBase.Tests.ps1 @@ -84,7 +84,11 @@ Describe 'ResourceBase\AssertProperties()' -Tag 'AssertProperties' { It 'Should not throw' { - { $mockResourceBaseInstance.AssertProperties() } | Should -Not -Throw + $mockDesiredState = @{ + MyProperty1 = 'MyValue1' + } + + { $mockResourceBaseInstance.AssertProperties($mockDesiredState) } | Should -Not -Throw } } diff --git a/tests/Unit/Private/Get-ClassName.Tests.ps1 b/tests/Unit/Private/Get-ClassName.Tests.ps1 new file mode 100644 index 000000000..97306cbee --- /dev/null +++ b/tests/Unit/Private/Get-ClassName.Tests.ps1 @@ -0,0 +1,109 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-ClassName' -Tag 'Private' { + Context 'When getting the class name' { + Context 'When passing value with named parameter' { + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-ClassName -InputObject ([System.UInt32] 3) + + $result.GetType().FullName | Should -Be 'System.Object[]' + + $result | Should -HaveCount 1 + $result | Should -Contain 'System.UInt32' + } + } + } + + Context 'When passing value in pipeline' { + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = ([System.UInt32] 3) | Get-ClassName + + $result.GetType().FullName | Should -Be 'System.Object[]' + + $result | Should -HaveCount 1 + $result | Should -Contain 'System.UInt32' + } + } + } + } + + Context 'When getting the class name and all inherited class names (base classes)' { + Context 'When passing value with named parameter' { + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-ClassName -InputObject ([System.UInt32] 3) -Recurse + + $result.GetType().FullName | Should -Be 'System.Object[]' + + $result | Should -HaveCount 2 + $result | Should -Contain 'System.UInt32' + $result | Should -Contain 'System.ValueType' + + $result[0] | Should -Be 'System.UInt32' + $result[1] | Should -Be 'System.ValueType' + } + } + } + + Context 'When passing value in pipeline' { + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = ([System.UInt32] 3) | Get-ClassName -Recurse + + $result.GetType().FullName | Should -Be 'System.Object[]' + + $result | Should -HaveCount 2 + $result | Should -Contain 'System.UInt32' + $result | Should -Contain 'System.ValueType' + + $result[0] | Should -Be 'System.UInt32' + $result[1] | Should -Be 'System.ValueType' + } + } + } + } +} diff --git a/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 b/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 new file mode 100644 index 000000000..5b223527f --- /dev/null +++ b/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 @@ -0,0 +1,99 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-DesiredStateProperty' -Tag 'Private' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it outside the here-string PowerShell + will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty()] + [System.String] + $MyResourceProperty3 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceProperty3 = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceReadProperty = 'MockReadValue1' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DesiredStateProperty -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceProperty2' -Because 'properties with $null values should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection even if they have values' + + $result.Keys | Should -Contain 'MyResourceKeyProperty1' -Because 'the property was set to a value in the mocked class' + $result.Keys | Should -Contain 'MyResourceProperty3' -Because 'the property was set to a value in the mocked class' + + $result.MyResourceKeyProperty1 | Should -Be 'MockValue1' + $result.MyResourceProperty3 | Should -Be 'MockValue3' + } + } +} diff --git a/tests/Unit/Private/Get-KeyProperty.Tests.ps1 b/tests/Unit/Private/Get-KeyProperty.Tests.ps1 new file mode 100644 index 000000000..f329c895c --- /dev/null +++ b/tests/Unit/Private/Get-KeyProperty.Tests.ps1 @@ -0,0 +1,100 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-KeyProperty' -Tag 'Private' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it outside the here-string PowerShell + will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty()] + [System.String] + $MyResourceProperty3 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceProperty3 = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceReadProperty = 'MockReadValue1' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-KeyProperty -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceProperty3' -Because 'non-key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' + + $result.Keys | Should -Contain 'MyResourceKeyProperty1' -Because 'the property is a key property' + $result.Keys | Should -Contain 'MyResourceKeyProperty2' -Because 'the property is a key property' + + $result.MyResourceKeyProperty1 | Should -Be 'MockValue1' + $result.MyResourceKeyProperty2 | Should -Be 'MockValue2' + } + } +} diff --git a/tests/Unit/Private/Get-LocalizedDataRecursive.Tests.ps1 b/tests/Unit/Private/Get-LocalizedDataRecursive.Tests.ps1 new file mode 100644 index 000000000..743118a16 --- /dev/null +++ b/tests/Unit/Private/Get-LocalizedDataRecursive.Tests.ps1 @@ -0,0 +1,161 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-LocalizedDataRecursive' -Tag 'Private' { + BeforeAll { + $getLocalizedData_ParameterFilter_Class = { + $FileName -eq 'MyClassResource.strings.psd1' + } + + $getLocalizedData_ParameterFilter_Base = { + $FileName -eq 'MyBaseClass.strings.psd1' + } + + Mock -CommandName Get-LocalizedData -MockWith { + return @{ + ClassStringKey = 'My class string' + } + } -ParameterFilter $getLocalizedData_ParameterFilter_Class + + Mock -CommandName Get-LocalizedData -MockWith { + return @{ + BaseStringKey = 'My base string' + } + } -ParameterFilter $getLocalizedData_ParameterFilter_Base + } + + Context 'When getting localization string for class name' { + Context 'When passing value with named parameter' { + It 'Should return the correct localization strings' { + InModuleScope -ScriptBlock { + $result = Get-LocalizedDataRecursive -ClassName 'MyClassResource' + + $result.Keys | Should -HaveCount 1 + $result.Keys | Should -Contain 'ClassStringKey' + } + + Should -Invoke -CommandName Get-LocalizedData -ParameterFilter $getLocalizedData_ParameterFilter_Class -Exactly -Times 1 -Scope It + } + } + + Context 'When passing value in pipeline' { + It 'Should return the correct localization strings' { + InModuleScope -ScriptBlock { + $result = 'MyClassResource' | Get-LocalizedDataRecursive + + $result.Keys | Should -HaveCount 1 + $result.Keys | Should -Contain 'ClassStringKey' + } + + Should -Invoke -CommandName Get-LocalizedData -ParameterFilter $getLocalizedData_ParameterFilter_Class -Exactly -Times 1 -Scope It + } + } + } + + Context 'When getting localization string for class and base name' { + Context 'When passing value with named parameter' { + It 'Should return the correct localization strings' { + InModuleScope -ScriptBlock { + $result = Get-LocalizedDataRecursive -ClassName @('MyClassResource','MyBaseClass') + + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'ClassStringKey' + $result.Keys | Should -Contain 'BaseStringKey' + } + + Should -Invoke -CommandName Get-LocalizedData -ParameterFilter $getLocalizedData_ParameterFilter_Class -Exactly -Times 1 -Scope It + } + } + + Context 'When passing value in pipeline' { + It 'Should return the correct localization strings' { + InModuleScope -ScriptBlock { + $result = @('MyClassResource','MyBaseClass') | Get-LocalizedDataRecursive + + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'ClassStringKey' + $result.Keys | Should -Contain 'BaseStringKey' + } + + Should -Invoke -CommandName Get-LocalizedData -ParameterFilter $getLocalizedData_ParameterFilter_Class -Exactly -Times 1 -Scope It + } + } + } + + Context 'When getting localization string for class and base file name' { + Context 'When passing value with named parameter' { + It 'Should return the correct localization strings' { + InModuleScope -ScriptBlock { + $result = Get-LocalizedDataRecursive -ClassName @( + 'MyClassResource.strings.psd1' + 'MyBaseClass.strings.psd1' + ) + + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'ClassStringKey' + $result.Keys | Should -Contain 'BaseStringKey' + } + + Should -Invoke -CommandName Get-LocalizedData -ParameterFilter $getLocalizedData_ParameterFilter_Class -Exactly -Times 1 -Scope It + } + } + + Context 'When passing value in pipeline' { + It 'Should return the correct localization strings' { + InModuleScope -ScriptBlock { + $result = @( + 'MyClassResource.strings.psd1' + 'MyBaseClass.strings.psd1' + ) | Get-LocalizedDataRecursive + + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'ClassStringKey' + $result.Keys | Should -Contain 'BaseStringKey' + } + + Should -Invoke -CommandName Get-LocalizedData -ParameterFilter $getLocalizedData_ParameterFilter_Class -Exactly -Times 1 -Scope It + } + } + } +} From fe16b77ddb2891ef830ac713d32fe2d03afcb1e1 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 30 Jun 2022 07:59:03 +0200 Subject: [PATCH 10/98] remove coverage file --- coverage.xml | 248 --------------------------------------------------- 1 file changed, 248 deletions(-) delete mode 100644 coverage.xml diff --git a/coverage.xml b/coverage.xml deleted file mode 100644 index 1166067ec..000000000 --- a/coverage.xml +++ /dev/null @@ -1,248 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 96cebac2cdd168363adf4fc527f0757adc45594a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 30 Jun 2022 10:19:35 +0200 Subject: [PATCH 11/98] Modify files --- source/Classes/001.ResourceBase.ps1 | 1 + source/Classes/002.DatabasePermission.ps1 | 15 ++- source/Classes/003.SqlDatabasePermission.ps1 | 77 ++++++++++-- source/Enum/1.DatabasePermissionState.ps1 | 10 ++ source/Enum/1.Ensure.ps1 | 5 + .../Public/Get-SqlDscDatabasePermission.ps1 | 118 ++++++++---------- .../en-US/SqlDatabasePermission.strings.psd1 | 2 +- source/en-US/SqlServerDsc.strings.psd1 | 3 + .../Classes/SqlDatabasePermission.Tests.ps1 | 2 +- 9 files changed, 153 insertions(+), 80 deletions(-) diff --git a/source/Classes/001.ResourceBase.ps1 b/source/Classes/001.ResourceBase.ps1 index 015cd34d1..eb6361ac3 100644 --- a/source/Classes/001.ResourceBase.ps1 +++ b/source/Classes/001.ResourceBase.ps1 @@ -28,6 +28,7 @@ class ResourceBase # Get all key properties. $keyProperty = $this | Get-KeyProperty + # TODO: TA BORT -VERBOSE Write-Verbose -Verbose -Message ($this.localizedData.GetCurrentState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) <# diff --git a/source/Classes/002.DatabasePermission.ps1 b/source/Classes/002.DatabasePermission.ps1 index 5e15bcb22..15fe4cf46 100644 --- a/source/Classes/002.DatabasePermission.ps1 +++ b/source/Classes/002.DatabasePermission.ps1 @@ -1,11 +1,22 @@ <# + .SYNOPSIS + The possible database permission states. + .PARAMETER State The state of the permission. .PARAMETER Permission The permissions to be granted or denied for the user in the database. -#> + .NOTES + The parameter State cannot use the enum [Microsoft.SqlServer.Management.Smo.PermissionState] + because we cannot know what assembly to use prior to loading the SqlServerDsc. + A user can either choose SqlServer och SQLPS. When we can move to only SqlServer + then the the module SqlServer can me loaded in the SqlServerDsc's module + manifest and then we could possible use the type [Microsoft.SqlServer.Management.Smo.PermissionState] + directly. Then the parameter Permission could also be of the type + [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]. +#> class DatabasePermission { [DscProperty(Key)] @@ -14,6 +25,6 @@ class DatabasePermission # TODO: Can we use a validate set for the permissions? [DscProperty(Mandatory)] - [System.String] + [System.String[]] $Permission } diff --git a/source/Classes/003.SqlDatabasePermission.ps1 b/source/Classes/003.SqlDatabasePermission.ps1 index 5b894cbe8..0ec47feac 100644 --- a/source/Classes/003.SqlDatabasePermission.ps1 +++ b/source/Classes/003.SqlDatabasePermission.ps1 @@ -67,7 +67,7 @@ class SqlDatabasePermission : ResourceBase [DscProperty()] [System.String] - $ServerName + $ServerName = (Get-ComputerName) [DscProperty(Mandatory)] [DatabasePermission[]] @@ -105,16 +105,77 @@ class SqlDatabasePermission : ResourceBase #> hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { - $currentEnsure = 'Absent' - - # TODO: Evaluate database permission current state - #(Get-DnsServerDsSetting @properties) - $currentState = @{ + Ensure = 'Absent' + ServerName = $this.ServerName InstanceName = $properties.InstanceName DatabaseName = $properties.DatabaseName - Name = $properties.Name - Ensure = $currentEnsure + Permission = [DatabasePermission[]] @() + Name = $properties.Name + } + + $sqlServerObject = Connect-SqlDscDatabaseEngine -ServerName $this.ServerName -InstanceName $properties.InstanceName + + # TA BORT -VERBOSE! + Write-Verbose -Verbose -Message ( + $script:localizedData.EvaluateDatabasePermissionForPrincipal -f @( + $properties.Name, + $properties.DatabaseName, + $properties.InstanceName + ) + ) + + $databasePermissionInfo = $sqlServerObject | + Get-SqlDscDatabasePermission -DatabaseName $this.DatabaseName -Name $this.Name -IgnoreMissingPrincipal + + if ($databasePermissionInfo) + { + $permissionState = $databasePermissionInfo | ForEach-Object -Process { + # Convert from the type PermissionState to String. + [System.String] $_.PermissionState + } | Select-Object -Unique + + foreach ($currentPermissionState in $permissionState) + { + $filteredDatabasePermission = $databasePermissionInfo | + Where-Object -FilterScript { + $_.PermissionState -eq $currentPermissionState + } + + $databasePermission = [DatabasePermission] @{ + State = $currentPermissionState + } + + # Initialize variable permission + [System.String[]] $statePermissionResult = @() + + foreach ($currentPermission in $filteredDatabasePermission) + { + $permissionProperty = ( + $currentPermission.PermissionType | + Get-Member -MemberType Property + ).Name + + foreach ($currentPermissionProperty in $permissionProperty) + { + if ($currentPermission.PermissionType."$currentPermissionProperty") + { + $statePermissionResult += $currentPermissionProperty + } + } + } + + <# + Sort and remove any duplicate permissions, also make sure + it is an array even if only one item. + #> + $databasePermission.Permission = @( + $statePermissionResult | + Sort-Object -Unique + ) + + $currentState.Permission += $databasePermission + } } return $currentState diff --git a/source/Enum/1.DatabasePermissionState.ps1 b/source/Enum/1.DatabasePermissionState.ps1 index c81b24f60..4a5e47efb 100644 --- a/source/Enum/1.DatabasePermissionState.ps1 +++ b/source/Enum/1.DatabasePermissionState.ps1 @@ -1,3 +1,13 @@ +<# + .SYNOPSIS + The possible database permission states. + + .NOTES + This is the permission states from the enum [Microsoft.SqlServer.Management.Smo.PermissionState] + except for the 'Revoke' which is handled by a resource is using 'Absent' for + the parameter Ensure. +#> + enum DatabasePermissionState { Grant diff --git a/source/Enum/1.Ensure.ps1 b/source/Enum/1.Ensure.ps1 index 9a57804c3..8f0c9e022 100644 --- a/source/Enum/1.Ensure.ps1 +++ b/source/Enum/1.Ensure.ps1 @@ -1,3 +1,8 @@ +<# + .SYNOPSIS + The possible states for the DSC resource parameter Ensure. +#> + enum Ensure { Present diff --git a/source/Public/Get-SqlDscDatabasePermission.ps1 b/source/Public/Get-SqlDscDatabasePermission.ps1 index 5448c511d..34330edb4 100644 --- a/source/Public/Get-SqlDscDatabasePermission.ps1 +++ b/source/Public/Get-SqlDscDatabasePermission.ps1 @@ -5,116 +5,98 @@ .PARAMETER ServerObject Specifies current server connection object. - # .PARAMETER InstanceName - # Specifies the SQL instance for the database. - .PARAMETER DatabaseName - Specifies the SQL database name. + Specifies the database name. .PARAMETER Name - Specifies the name of the database principal for which the permission set is returned. + Specifies the name of the database principal for which the permissions are + returned. - # .PARAMETER PermissionState - # This is the state of permission set. Valid values are 'Grant' or 'Deny'. + .PARAMETER IgnoreMissingPrincipal + Specifies that the command ignores if the database principal do not exist + which also include if database is not present. + If not passed the command throws an error if the database or database + principal is missing. - # .PARAMETER Permissions - # This is a list that represents a SQL Server set of database permissions. + .OUTPUTS + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] .NOTES - This command excludes fixed roles like db_datareader. - - TODO: This function will not throw an error if for example the database - does not exist, so that the Get() method of the resource does not - throw. Suggest adding optional parmeter 'FailOnError', - or 'EvaluateMandatoryProperties', or a combination. + This command excludes fixed roles like db_datareader, and will always return + $null for such roles. #> function Get-SqlDscDatabasePermission { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because Script Analyzer does not understand type even if cast when using comma in return statement')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] [CmdletBinding()] - [OutputType([System.String[]])] + [OutputType([Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Microsoft.SqlServer.Management.Smo.Server] $ServerObject, - # [Parameter(Mandatory = $true)] - # [System.String] - # $InstanceName, - [Parameter(Mandatory = $true)] [System.String] $DatabaseName, [Parameter(Mandatory = $true)] [System.String] - $Name + $Name, - # [Parameter(Mandatory = $true)] - # [ValidateSet('Grant', 'Deny', 'GrantWithGrant')] - # [System.String] - # $PermissionState, - - # [Parameter(Mandatory = $true)] - # [System.String[]] - # $Permissions, - - # [Parameter()] - # [ValidateNotNullOrEmpty()] - # [System.String] - # $ServerName = (Get-ComputerName) + [Parameter()] + [System.Management.Automation.SwitchParameter] + $IgnoreMissingPrincipal ) # Initialize variable permission - [System.String[]] $getSqlDatabasePermissionResult = @() + $getSqlDscDatabasePermissionResult = $null $sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName] if ($sqlDatabaseObject) { - $isDatabasePrincipal = Test-SqlDscIsDatabasePrincipal @PSBoundParameters -ExcludeFixedRoles + $testSqlDscIsDatabasePrincipalParameters = @{ + ServerObject = $ServerObject + DatabaseName = $DatabaseName + Name = $Name + ExcludeFixedRoles = $true + } + + $isDatabasePrincipal = Test-SqlDscIsDatabasePrincipal @testSqlDscIsDatabasePrincipalParameters if ($isDatabasePrincipal) { - $databasePermissionInfo = $sqlDatabaseObject.EnumDatabasePermissions($Name) | - Where-Object -FilterScript { - $_.PermissionState -eq $PermissionState - } - - if ($databasePermissionInfo) - { - foreach ($currentDatabasePermissionInfo in $databasePermissionInfo) - { - $permissionProperty = ( - $currentDatabasePermissionInfo.PermissionType | - Get-Member -MemberType Property - ).Name - - foreach ($currentPermissionProperty in $permissionProperty) - { - if ($currentDatabasePermissionInfo.PermissionType."$currentPermissionProperty") - { - $getSqlDatabasePermissionResult += $currentPermissionProperty - } - } - - # Remove any duplicate permissions. - $getSqlDatabasePermissionResult = @( - $getSqlDatabasePermissionResult | - Sort-Object -Unique - ) - } - } + $getSqlDscDatabasePermissionResult = $sqlDatabaseObject.EnumDatabasePermissions($Name) } else { - Write-Verbose -Message ("The database principal '{0}' is neither a user, database role (user-defined), or database application role in the database '{1}'. (GETSDP0001)." -f $Name, $DatabaseName) + $missingPrincipalMessage = $script:localizedData.DatabasePermissionMissingPrincipal -f $Name, $DatabaseName + + if ($IgnoreMissingPrincipal.IsPresent) + { + Write-Verbose -Message $missingPrincipalMessage + } + else + { + throw $missingPrincipalMessage + } } } else { - Write-Verbose -Message ("The database '{0}' did not exist. (GETSDP0002)" -f $DatabaseName) + $missingPrincipalMessage = $script:localizedData.DatabasePermissionMissingDatabase -f $DatabaseName + + if ($IgnoreMissingPrincipal.IsPresent) + { + Write-Verbose -Message $missingPrincipalMessage + } + else + { + throw $missingPrincipalMessage + } } - return [System.String[]] $getSqlDatabasePermissionResult + return , $getSqlDscDatabasePermissionResult } diff --git a/source/en-US/SqlDatabasePermission.strings.psd1 b/source/en-US/SqlDatabasePermission.strings.psd1 index af9d7a5b9..733d8f7c7 100644 --- a/source/en-US/SqlDatabasePermission.strings.psd1 +++ b/source/en-US/SqlDatabasePermission.strings.psd1 @@ -9,8 +9,8 @@ ConvertFrom-StringData @' # None # Strings directly used by the derived class SqlDatabasePermission. + EvaluateDatabasePermissionForPrincipal = Evaluate the current permissions for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0001) - GetDatabasePermission = Get permissions for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0001) DatabaseNotFound = The database '{0}' does not exist. (SDP0002) ChangePermissionForUser = Changing the permission for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0003) NameIsMissing = The name '{0}' is neither a database user, database role (user-defined), or database application role in the database '{1}'. (SDP0004) diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 12f23e4ca..3dfef20a8 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -6,4 +6,7 @@ #> ConvertFrom-StringData @' + # Get-SqlDscDatabasePermission + DatabasePermissionMissingPrincipal = The database principal '{0}' is neither a user, database role (user-defined), or database application role in the database '{1}'. (GETSDP0001). + DatabasePermissionMissingDatabase = The database '{0}' did not exist. (GETSDP0002) '@ diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index 171d26c67..d8ddd466d 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -70,7 +70,7 @@ Describe 'SqlDatabasePermission' { } } - It 'SHould have a default or empty constructor' { + It 'Should have a default or empty constructor' { InModuleScope -ScriptBlock { $instance = [SqlDatabasePermission]::new() $instance | Should -Not -BeNullOrEmpty From 8a25e3dcf7ea25a9359b858a35b84f8c42e3b9c9 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 30 Jun 2022 18:09:39 +0200 Subject: [PATCH 12/98] push latest changes --- source/Classes/001.ResourceBase.ps1 | 2 + source/Classes/003.SimpleResource.ps1 | 91 +++++++++++++++++++ source/Classes/003.SqlDatabasePermission.ps1 | 4 +- .../4-InvokeClassResource.ps1 | 19 ++++ 4 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 source/Classes/003.SimpleResource.ps1 create mode 100644 source/Examples/Resources/SqlDatabasePermission/4-InvokeClassResource.ps1 diff --git a/source/Classes/001.ResourceBase.ps1 b/source/Classes/001.ResourceBase.ps1 index eb6361ac3..16f874afa 100644 --- a/source/Classes/001.ResourceBase.ps1 +++ b/source/Classes/001.ResourceBase.ps1 @@ -40,6 +40,8 @@ class ResourceBase $getCurrentStateResult = $this.GetCurrentState($keyProperty) + Write-Verbose -Verbose -Message ($getCurrentStateResult | Out-String) + $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) foreach ($propertyName in $this.PSObject.Properties.Name) diff --git a/source/Classes/003.SimpleResource.ps1 b/source/Classes/003.SimpleResource.ps1 new file mode 100644 index 000000000..dc41fa372 --- /dev/null +++ b/source/Classes/003.SimpleResource.ps1 @@ -0,0 +1,91 @@ +<# + .SYNOPSIS + Resource for testing. +#> + +[DscResource()] +class SimpleResource +{ + [DscProperty(Key)] + [System.String] + $InstanceName + + [DscProperty(Key)] + [System.String] + $DatabaseName + + [DscProperty(Key)] + [System.String] + $Name + + [DscProperty()] + [System.String] + $ServerName = (Get-ComputerName) + + [DscProperty(Mandatory)] + [DatabasePermission[]] + $Permission + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + # [DscProperty(NotConfigurable)] + # [Reason[]] + # $Reasons + + [SimpleResource] Get() + { + $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) + + $dscResourceObject.InstanceName = 'SQL2017' + $dscResourceObject.DatabaseName = 'MyDB' + $dscResourceObject.Name = 'MyPrincipal' + $dscResourceObject.ServerName = 'MyHost' + $dscResourceObject.Ensure = 'Present' + + # Attempt 1 + <# + $dscResourceObject.Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('CONNECT') + } + ) + #> + + # Attempt 2 + <# + $dscResourceObject.Permission = [CimInstance[]] @( + ( + New-CimInstance -ClientOnly -ClassName 'DatabasePermission' -Namespace 'root/microsoft/windows/desiredstateconfiguration' -Property @{ + State = 'Grant' + Permission = @('CONNECT') + } + ) + ) + #> + + # Attempt 3 + <# + $dscResourceObject.Permission = [DatabasePermission[]] @( + ( + New-CimInstance -ClientOnly -Namespace 'root/Microsoft/Windows/DesiredStateConfiguration' -ClassName 'DatabasePermission' -Property @{ + State = 'Grant' + Permission = @('CONNECT') + } + ) + ) + #> + return $dscResourceObject + } + + [System.Boolean] Test() + { + return $true + } + + [void] Set() + { + } +} diff --git a/source/Classes/003.SqlDatabasePermission.ps1 b/source/Classes/003.SqlDatabasePermission.ps1 index 0ec47feac..fc802ad11 100644 --- a/source/Classes/003.SqlDatabasePermission.ps1 +++ b/source/Classes/003.SqlDatabasePermission.ps1 @@ -118,7 +118,7 @@ class SqlDatabasePermission : ResourceBase # TA BORT -VERBOSE! Write-Verbose -Verbose -Message ( - $script:localizedData.EvaluateDatabasePermissionForPrincipal -f @( + $this.localizedData.EvaluateDatabasePermissionForPrincipal -f @( $properties.Name, $properties.DatabaseName, $properties.InstanceName @@ -174,7 +174,7 @@ class SqlDatabasePermission : ResourceBase Sort-Object -Unique ) - $currentState.Permission += $databasePermission + [DatabasePermission[]] $currentState.Permission += $databasePermission } } diff --git a/source/Examples/Resources/SqlDatabasePermission/4-InvokeClassResource.ps1 b/source/Examples/Resources/SqlDatabasePermission/4-InvokeClassResource.ps1 new file mode 100644 index 000000000..0ead3fcfe --- /dev/null +++ b/source/Examples/Resources/SqlDatabasePermission/4-InvokeClassResource.ps1 @@ -0,0 +1,19 @@ +# $a = Invoke-DscResource -ModuleName SqlServerDsc -Name SqlDatabasePermission -Method Get -Property @{ +# Ensure = 'Present' +# ServerName = 'localhost' +# InstanceName = 'sql2017' +# DatabaseName = 'AdventureWorks2' +# Name = 'SQLTEST\sqluser' +# Permission = [CimInstance[]]@( +# (New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ +# State = 'Grant' +# Permission = @('select') +# }) +# (New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ +# State = 'GrantWithGrant' +# Permission = @('update') +# }) +# ) + +# PSDscRunAsCredential = $SqlInstallCredential +# } -Verbose From dba7a6d8e1b32c48c52f68297176a34aa0f8252f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 1 Jul 2022 09:54:26 +0200 Subject: [PATCH 13/98] Fix code --- debug.ps1 | 1125 +++++++++++++++++ source/Classes/002.DatabasePermission.ps1 | 15 +- source/Classes/003.SimpleResource.ps1 | 91 -- ....ResourceBase.ps1 => 010.ResourceBase.ps1} | 0 source/Classes/020.SimpleResource.ps1 | 68 + ...sion.ps1 => 020.SqlDatabasePermission.ps1} | 15 + source/Enum/1.DatabasePermissionState.ps1 | 16 - 7 files changed, 1215 insertions(+), 115 deletions(-) create mode 100644 debug.ps1 delete mode 100644 source/Classes/003.SimpleResource.ps1 rename source/Classes/{001.ResourceBase.ps1 => 010.ResourceBase.ps1} (100%) create mode 100644 source/Classes/020.SimpleResource.ps1 rename source/Classes/{003.SqlDatabasePermission.ps1 => 020.SqlDatabasePermission.ps1} (90%) delete mode 100644 source/Enum/1.DatabasePermissionState.ps1 diff --git a/debug.ps1 b/debug.ps1 new file mode 100644 index 000000000..d4e18f280 --- /dev/null +++ b/debug.ps1 @@ -0,0 +1,1125 @@ +#Region '.\prefix.ps1' 0 +$script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/DscResource.Common' +Import-Module -Name $script:dscResourceCommonModulePath + +# TODO: The goal would be to remove this, when no classes and public or private functions need it. +$script:sqlServerDscCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/SqlServerDsc.Common' +Import-Module -Name $script:sqlServerDscCommonModulePath + +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' +#EndRegion '.\prefix.ps1' 9 +#Region '.\Enum\1.Ensure.ps1' 0 +<# + .SYNOPSIS + The possible states for the DSC resource parameter Ensure. +#> + +enum Ensure +{ + Present + Absent +} +#EndRegion '.\Enum\1.Ensure.ps1' 11 + +#Region '.\Classes\002.DatabasePermission.ps1' 0 +<# + .SYNOPSIS + The possible database permission states. + + .PARAMETER State + The state of the permission. + + .PARAMETER Permission + The permissions to be granted or denied for the user in the database. + + .NOTES + The DSC properties specifies attributes Key and Required, those attributes + are not honored during compilation in the current implementation of + PowerShell DSC. They are kept here so when they do get honored it will help + detect missing properties during compilation. The Key property is evaluate + during runtime so that no two states are enforcing the same permission. +#> +class DatabasePermission +{ + [DscProperty(Key)] + [ValidateSet('Grant', 'GrantWithGrant', 'Deny')] + [System.String] + $State + + # TODO: Can we use a validate set for the permissions? + [DscProperty(Mandatory)] + [System.String[]] + $Permission +} +#EndRegion '.\Classes\002.DatabasePermission.ps1' 30 +#Region '.\Classes\002.Reason.ps1' 0 +class Reason +{ + [DscProperty()] + [System.String] + $Code + + [DscProperty()] + [System.String] + $Phrase +} +#EndRegion '.\Classes\002.Reason.ps1' 11 +#Region '.\Classes\003.SimpleResource.ps1' 0 +<# + .SYNOPSIS + Resource for testing. +#> + +[DscResource()] +class SimpleResource +{ + [DscProperty(Key)] + [System.String] + $InstanceName + + [DscProperty(Key)] + [System.String] + $DatabaseName + + [DscProperty(Key)] + [System.String] + $Name + + [DscProperty()] + [System.String] + $ServerName = (Get-ComputerName) + + [DscProperty(Mandatory)] + [DatabasePermission[]] + $Permission + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [SimpleResource] Get() + { + $dscResourceObject = [SimpleResource] @{ + InstanceName = 'SQL2017' + DatabaseName = 'MyDB' + Name = 'MyPrincipal' + ServerName = 'MyHost' + Ensure = 'Present' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('CONNECT') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @('SELECT') + } + ) + Reasons = [Reason[]] @( + [Reason] @{ + Code = '{0}:{0}:Ensure' -f $this.GetType() + Phrase = 'The property Ensure should be Present, but was Absent' + } + ) + } + + return $dscResourceObject + } + + [System.Boolean] Test() + { + return $true + } + + [void] Set() + { + } +} +#EndRegion '.\Classes\003.SimpleResource.ps1' 69 +#Region '.\Classes\001.ResourceBase.ps1' 0 +<# + .SYNOPSIS + A class with methods that are equal for all class-based resources. + + .DESCRIPTION + A class with methods that are equal for all class-based resources. + + .NOTES + This class should not contain any DSC properties. +#> + +class ResourceBase +{ + # Hidden property for holding localization strings + hidden [System.Collections.Hashtable] $localizedData = @{} + + # Default constructor + ResourceBase() + { + # TODO: When this fails the LCM returns 'Failed to create an object of PowerShell class SqlDatabasePermission' instead of the actual error that occurred. + $this.localizedData = Get-LocalizedDataRecursive -ClassName ($this | Get-ClassName -Recurse) + } + + [ResourceBase] Get() + { + $this.Assert() + + # Get all key properties. + $keyProperty = $this | Get-KeyProperty + + # TODO: TA BORT -VERBOSE + Write-Verbose -Verbose -Message ($this.localizedData.GetCurrentState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + + <# + TODO: Should call back to the derived class for proper handling of adding + additional parameters to the variable $keyProperty that needs to be + passed to GetCurrentState(). + #> + #$specialKeyProperty = @() + + $getCurrentStateResult = $this.GetCurrentState($keyProperty) + + Write-Verbose -Verbose -Message ($getCurrentStateResult | Out-String) + + $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) + + + $dscResourceObject.InstanceName = 'SQL2017' + $dscResourceObject.DatabaseName = 'MyDB' + $dscResourceObject.Name = 'MyPrincipal' + $dscResourceObject.ServerName = 'MyHost' + $dscResourceObject.Ensure = 'Present' + $dscResourceObject.Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('CONNECT') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @('SELECT') + } + ) + $dscResourceObject.Reasons = [Reason[]] @( + [Reason] @{ + Code = '{0}:{0}:Ensure' -f $this.GetType() + Phrase = 'The property Ensure should be Present, but was Absent' + } + ) + + + return $dscResourceObject + + $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) + + foreach ($propertyName in $this.PSObject.Properties.Name) + { + Write-Verbose -Verbose -Message '----------------------------' + Write-Verbose -Verbose -Message $propertyName + + if ($propertyName -in @($getCurrentStateResult.Keys)) + { + Write-verbose -Verbose -Message ($getCurrentStateResult.$propertyName.GetType().FullName | Out-String) + Write-verbose -Verbose -Message ($getCurrentStateResult.$propertyName.GetType().IsArray | Out-String) + Write-verbose -Verbose -Message ($getCurrentStateResult.$propertyName.GetType().Module.ScopeName | Out-String) + #Write-verbose -Verbose -Message ($getCurrentStateResult.$propertyName.GetType() | fl * | Out-String) + + if ($propertyName -eq 'Permission') + { + Write-verbose -Verbose -Message 'HARDCODE PERMISSION' + + $dscResourceObject.Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('CONNECT') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @('SELECT') + } + ) + } + else + { + $dscResourceObject.$propertyName = $getCurrentStateResult.$propertyName -as $getCurrentStateResult.$propertyName.GetType().FullName + } + } + } + + # Return properties. + return $dscResourceObject + } + + [void] Set() + { + # Get all key properties. + $keyProperty = $this | Get-KeyProperty + + Write-Verbose -Verbose -Message ($this.localizedData.SetDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + + $this.Assert() + + # Call the Compare method to get enforced properties that are not in desired state. + $propertiesNotInDesiredState = $this.Compare() + + if ($propertiesNotInDesiredState) + { + $propertiesToModify = $this.GetDesiredStateForSplatting($propertiesNotInDesiredState) + + $propertiesToModify.Keys | ForEach-Object -Process { + Write-Verbose -Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_) + } + + <# + Call the Modify() method with the properties that should be enforced + and was not in desired state. + #> + $this.Modify($propertiesToModify) + } + else + { + Write-Verbose -Verbose -Message $this.localizedData.NoPropertiesToSet + } + } + + [System.Boolean] Test() + { + # Get all key properties. + $keyProperty = $this | Get-KeyProperty + + Write-Verbose -Verbose -Message ($this.localizedData.TestDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + + $this.Assert() + + $isInDesiredState = $true + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + $propertiesNotInDesiredState = $this.Compare() + + if ($propertiesNotInDesiredState) + { + $isInDesiredState = $false + } + + if ($isInDesiredState) + { + Write-Verbose -Verbose -Message $this.localizedData.InDesiredState + } + else + { + Write-Verbose -Verbose -Message $this.localizedData.NotInDesiredState + } + + return $isInDesiredState + } + + <# + Returns a hashtable containing all properties that should be enforced and + are not in desired state. + + This method should normally not be overridden. + #> + hidden [System.Collections.Hashtable[]] Compare() + { + $currentState = $this.Get() | ConvertFrom-DscResourceInstance + $desiredState = $this | Get-DesiredStateProperty + + $CompareDscParameterState = @{ + CurrentValues = $currentState + DesiredValues = $desiredState + Properties = $desiredState.Keys + ExcludeProperties = @('DnsServer') + IncludeValue = $true + } + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + return (Compare-DscParameterState @CompareDscParameterState) + } + + # Returns a hashtable containing all properties that should be enforced. + hidden [System.Collections.Hashtable] GetDesiredStateForSplatting([System.Collections.Hashtable[]] $Properties) + { + $desiredState = @{} + + $Properties | ForEach-Object -Process { + $desiredState[$_.Property] = $_.ExpectedValue + } + + return $desiredState + } + + # This method should normally not be overridden. + hidden [void] Assert() + { + $desiredState = $this | Get-DesiredStateProperty + + $this.AssertProperties($desiredState) + } + + <# + This method can be overridden if resource specific property asserts are + needed. The parameter properties will contain the properties that was + passed a value. + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidEmptyNamedBlocks', '')] + hidden [void] AssertProperties([System.Collections.Hashtable] $properties) + { + } + + <# + This method must be overridden by a resource. The parameter properties will + contain the properties that should be enforced and that are not in desired + state. + #> + hidden [void] Modify([System.Collections.Hashtable] $properties) + { + throw $this.localizedData.ModifyMethodNotImplemented + } + + <# + This method must be overridden by a resource. The parameter properties will + contain the key properties. + #> + hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + throw $this.localizedData.GetCurrentStateMethodNotImplemented + } +} +#EndRegion '.\Classes\001.ResourceBase.ps1' 200 + +#Region '.\Classes\003.SqlDatabasePermission.ps1' 0 +<# + .SYNOPSIS + The `SqlDatabasePermission` DSC resource is used to grant, deny or revoke + permissions for a user in a database + + .DESCRIPTION + The `SqlDatabasePermission` DSC resource is used to grant, deny or revoke + permissions for a user in a database. For more information about permissions, + please read the article [Permissions (Database Engine)](https://docs.microsoft.com/en-us/sql/relational-databases/security/permissions-database-engine). + + >**Note:** When revoking permission with PermissionState 'GrantWithGrant', both the + >grantee and _all the other users the grantee has granted the same permission to_, + >will also get their permission revoked. + + Valid permission names can be found in the article [DatabasePermissionSet Class properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.databasepermissionset#properties). + + ## Requirements + + * Target machine must be running Windows Server 2012 or later. + * Target machine must be running SQL Server Database Engine 2012 or later. + + ## 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+SqlDatabasePermission). + + .PARAMETER InstanceName + The name of the SQL Server instance to be configured. Default value is + 'MSSQLSERVER'. + + .PARAMETER DatabaseName + The name of the database. + + .PARAMETER Name + The name of the user that should be granted or denied the permission. + + .PARAMETER ServerName + The host name of the SQL Server to be configured. Default value is the + current computer name. + + .PARAMETER Permission + An array of database permissions to enforce. + + This is an array of CIM instances of class `DatabasePermission` from the + namespace `root/Microsoft/Windows/DesiredStateConfiguration`. + + .PARAMETER Ensure + If the permission should be granted ('Present') or revoked ('Absent'). + + .PARAMETER Reasons + Returns the reason a property is not in desired state. +#> + +[DscResource()] +class SqlDatabasePermission : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $InstanceName + + [DscProperty(Key)] + [System.String] + $DatabaseName + + [DscProperty(Key)] + [System.String] + $Name + + [DscProperty()] + [System.String] + $ServerName = (Get-ComputerName) + + [DscProperty(Mandatory)] + [DatabasePermission[]] + $Permission + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [SqlDatabasePermission] Get() + { + $dscResourceObject = [SimpleResource] @{ + InstanceName = 'SQL2017' + DatabaseName = 'MyDB' + Name = 'MyPrincipal' + ServerName = 'MyHost' + Ensure = 'Present' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('CONNECT') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @('SELECT') + } + ) + Reasons = [Reason[]] @( + [Reason] @{ + Code = '{0}:{0}:Ensure' -f $this.GetType() + Phrase = 'The property Ensure should be Present, but was Absent' + } + ) + } + + return $dscResourceObject + + # Call the base method to return the properties. + return ([ResourceBase] $this).Get() + } + + [System.Boolean] Test() + { + # Call the base method to test all of the properties that should be enforced. + return ([ResourceBase] $this).Test() + } + + [void] Set() + { + # Call the base method to enforce the properties. + ([ResourceBase] $this).Set() + } + + <# + Base method Get() call this method to get the current state as a hashtable. + The parameter properties will contain the key properties. + #> + hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + $currentState = @{ + Ensure = 'Absent' + ServerName = $this.ServerName + InstanceName = $properties.InstanceName + DatabaseName = $properties.DatabaseName + Permission = [DatabasePermission[]] @() + Name = $properties.Name + } + + $sqlServerObject = Connect-SqlDscDatabaseEngine -ServerName $this.ServerName -InstanceName $properties.InstanceName + + # TA BORT -VERBOSE! + Write-Verbose -Verbose -Message ( + $this.localizedData.EvaluateDatabasePermissionForPrincipal -f @( + $properties.Name, + $properties.DatabaseName, + $properties.InstanceName + ) + ) + + $databasePermissionInfo = $sqlServerObject | + Get-SqlDscDatabasePermission -DatabaseName $this.DatabaseName -Name $this.Name -IgnoreMissingPrincipal + + if ($databasePermissionInfo) + { + $permissionState = $databasePermissionInfo | ForEach-Object -Process { + # Convert from the type PermissionState to String. + [System.String] $_.PermissionState + } | Select-Object -Unique + + foreach ($currentPermissionState in $permissionState) + { + $filteredDatabasePermission = $databasePermissionInfo | + Where-Object -FilterScript { + $_.PermissionState -eq $currentPermissionState + } + + $databasePermission = [DatabasePermission] @{ + State = $currentPermissionState + } + + # Initialize variable permission + [System.String[]] $statePermissionResult = @() + + foreach ($currentPermission in $filteredDatabasePermission) + { + $permissionProperty = ( + $currentPermission.PermissionType | + Get-Member -MemberType Property + ).Name + + foreach ($currentPermissionProperty in $permissionProperty) + { + if ($currentPermission.PermissionType."$currentPermissionProperty") + { + $statePermissionResult += $currentPermissionProperty + } + } + } + + <# + Sort and remove any duplicate permissions, also make sure + it is an array even if only one item. + #> + $databasePermission.Permission = @( + $statePermissionResult | + Sort-Object -Unique + ) + + [DatabasePermission[]] $currentState.Permission += $databasePermission + } + } + + return $currentState + } + + <# + Base method Set() call this method with the properties that should be + enforced and that are not in desired state. + #> + hidden [void] Modify([System.Collections.Hashtable] $properties) + { + Write-Verbose -Message ($properties | Out-String) -Verbose + + #Set-DnsServerDsSetting @properties + } + + <# + Base method Assert() call this method with the properties that was passed + a value. + #> + hidden [void] AssertProperties([System.Collections.Hashtable] $properties) + { + # @( + # 'DirectoryPartitionAutoEnlistInterval', + # 'TombstoneInterval' + # ) | ForEach-Object -Process { + # $valueToConvert = $this.$_ + + # # Only evaluate properties that have a value. + # if ($null -ne $valueToConvert) + # { + # Assert-TimeSpan -PropertyName $_ -Value $valueToConvert -Minimum '0.00:00:00' + # } + # } + } +} +#EndRegion '.\Classes\003.SqlDatabasePermission.ps1' 215 +#Region '.\Private\Get-ClassName.ps1' 0 +<# + .SYNOPSIS + Get the class name of the passed object, and optional an array with + all inherited classes. + + .PARAMETER InputObject + The object to be evaluated. + + .OUTPUTS + Returns a string array with at least one item. +#> +function Get-ClassName +{ + [CmdletBinding()] + [OutputType([System.Object[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Recurse + ) + + # Create a list of the inherited class names + $class = @($InputObject.GetType().FullName) + + if ($Recurse.IsPresent) + { + $parentClass = $InputObject.GetType().BaseType + + while ($parentClass -ne [System.Object]) + { + $class += $parentClass.FullName + + $parentClass = $parentClass.BaseType + } + } + + return ,$class +} +#EndRegion '.\Private\Get-ClassName.ps1' 44 +#Region '.\Private\Get-DesiredStateProperty.ps1' 0 + +<# + .SYNOPSIS + Returns the properties that should be enforced for the desired state. + + .DESCRIPTION + Returns the properties that should be enforced for the desired state. + This function converts a PSObject into a hashtable containing the properties + that should be enforced. + + .PARAMETER InputObject + The object that contain the properties with the desired state. + + .OUTPUTS + Hashtable +#> +function Get-DesiredStateProperty +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject + ) + + $desiredStateProperty = $InputObject | ConvertFrom-DscResourceInstance + + <# + Remove properties that have $null as the value, and remove read + properties so that there is no chance to compare those. + #> + @($desiredStateProperty.Keys) | ForEach-Object -Process { + $isReadProperty = $InputObject.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'NotConfigurable' }).NamedArguments.TypedValue.Value -eq $true + + if ($isReadProperty -or $null -eq $desiredStateProperty[$_]) + { + $desiredStateProperty.Remove($_) + } + } + + return $desiredStateProperty +} +#EndRegion '.\Private\Get-DesiredStateProperty.ps1' 45 +#Region '.\Private\Get-KeyProperty.ps1' 0 + +<# + .SYNOPSIS + Returns the DSC resource key property and its value. + + .DESCRIPTION + Returns the DSC resource key property and its value. + + .PARAMETER InputObject + The object that contain the key property. + + .OUTPUTS + Hashtable +#> +function Get-KeyProperty +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject + ) + + # Get all key properties. + $keyProperty = $InputObject | + Get-Member -MemberType 'Property' | + Select-Object -ExpandProperty 'Name' | + Where-Object -FilterScript { + $InputObject.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'Key' }).NamedArguments.TypedValue.Value -eq $true + } + + # Return a hashtable containing each key property and its value. + $getKeyPropertyResult = @{} + + $keyProperty | ForEach-Object -Process { + $getKeyPropertyResult.$_ = $InputObject.$_ + } + + return $getKeyPropertyResult +} +#EndRegion '.\Private\Get-KeyProperty.ps1' 43 +#Region '.\Private\Get-LocalizedDataRecursive.ps1' 0 +<# + .SYNOPSIS + Get the localization strings data from one or more localization string files. + This can be used in classes to be able to inherit localization strings + from one or more parent (base) classes. + + The order of class names passed to parameter `ClassName` determines the order + of importing localization string files. First entry's localization string file + will be imported first, then next entry's localization string file, and so on. + If the second (or any consecutive) entry's localization string file contain a + localization string key that existed in a previous imported localization string + file that localization string key will be ignored. Making it possible for a + child class to override localization strings from one or more parent (base) + classes. + + .PARAMETER ClassName + An array of class names, normally provided by `Get-ClassName -Recurse`. + + .OUTPUTS + Returns a string array with at least one item. +#> +function Get-LocalizedDataRecursive +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.String[]] + $ClassName + ) + + begin + { + $localizedData = @{} + } + + process + { + foreach ($name in $ClassName) + { + if ($name -match '\.psd1') + { + # Assume we got full file name. + $localizationFileName = $name + } + else + { + # Assume we only got class name. + $localizationFileName = '{0}.strings.psd1' -f $name + } + + Write-Debug -Message ('Importing localization data from {0}' -f $localizationFileName) + + # Get localized data for the class + $classLocalizationStrings = Get-LocalizedData -DefaultUICulture 'en-US' -FileName $localizationFileName -ErrorAction 'Stop' + + # Append only previously unspecified keys in the localization data + foreach ($key in $classLocalizationStrings.Keys) + { + if (-not $localizedData.ContainsKey($key)) + { + $localizedData[$key] = $classLocalizationStrings[$key] + } + } + } + } + + end + { + Write-Debug -Message ('Localization data: {0}' -f ($localizedData | ConvertTo-JSON)) + + return $localizedData + } +} +#EndRegion '.\Private\Get-LocalizedDataRecursive.ps1' 76 +#Region '.\Public\Connect-SqlDscDatabaseEngine.ps1' 0 +<# + .SYNOPSIS + Connect to a SQL Server Database Engine and return the server object. + + .PARAMETER ServerName + String containing the host name of the SQL Server to connect to. + Default value is the current computer name. + + .PARAMETER InstanceName + String containing the SQL Server Database Engine instance to connect to. + Default value is 'MSSQLSERVER'. + + .PARAMETER Credential + The credentials to use to impersonate a user when connecting to the + SQL Server Database Engine instance. If this parameter is left out, then + the current user will be used to connect to the SQL Server Database Engine + instance using Windows Integrated authentication. + + .PARAMETER LoginType + Specifies which type of logon credential should be used. The valid types + are 'WindowsUser' or 'SqlLogin'. Default value is 'WindowsUser' + If set to 'WindowsUser' then the it will impersonate using the Windows + login specified in the parameter Credential. + If set to 'WindowsUser' then the it will impersonate using the native SQL + login specified in the parameter Credential. + + .PARAMETER StatementTimeout + Set the query StatementTimeout in seconds. Default 600 seconds (10 minutes). + + .EXAMPLE + Connect-SqlDscDatabaseEngine + + Connects to the default instance on the local server. + + .EXAMPLE + Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + + Connects to the instance 'MyInstance' on the local server. + + .EXAMPLE + Connect-SqlDscDatabaseEngine -ServerName 'sql.company.local' -InstanceName 'MyInstance' + + Connects to the instance 'MyInstance' on the server 'sql.company.local'. +#> +function Connect-SqlDscDatabaseEngine +{ + [CmdletBinding(DefaultParameterSetName = 'SqlServer')] + param + ( + [Parameter(ParameterSetName = 'SqlServer')] + [Parameter(ParameterSetName = 'SqlServerWithCredential')] + [ValidateNotNull()] + [System.String] + $ServerName = (Get-ComputerName), + + [Parameter(ParameterSetName = 'SqlServer')] + [Parameter(ParameterSetName = 'SqlServerWithCredential')] + [ValidateNotNull()] + [System.String] + $InstanceName = 'MSSQLSERVER', + + [Parameter(ParameterSetName = 'SqlServerWithCredential', Mandatory = $true)] + [ValidateNotNull()] + [Alias('SetupCredential', 'DatabaseCredential')] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter(ParameterSetName = 'SqlServerWithCredential')] + [ValidateSet('WindowsUser', 'SqlLogin')] + [System.String] + $LoginType = 'WindowsUser', + + [Parameter()] + [ValidateNotNull()] + [System.Int32] + $StatementTimeout = 600 + ) + + # Call the private function. + return (Connect-Sql @PSBoundParameters) +} +#EndRegion '.\Public\Connect-SqlDscDatabaseEngine.ps1' 82 +#Region '.\Public\Get-SqlDscDatabasePermission.ps1' 0 +<# + .SYNOPSIS + Returns the current permissions for the database principal. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER DatabaseName + Specifies the database name. + + .PARAMETER Name + Specifies the name of the database principal for which the permissions are + returned. + + .PARAMETER IgnoreMissingPrincipal + Specifies that the command ignores if the database principal do not exist + which also include if database is not present. + If not passed the command throws an error if the database or database + principal is missing. + + .OUTPUTS + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] + + .NOTES + This command excludes fixed roles like db_datareader, and will always return + $null for such roles. +#> +function Get-SqlDscDatabasePermission +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because Script Analyzer does not understand type even if cast when using comma in return statement')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] + [CmdletBinding()] + [OutputType([Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $DatabaseName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $IgnoreMissingPrincipal + ) + + # Initialize variable permission + $getSqlDscDatabasePermissionResult = $null + + $sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName] + + if ($sqlDatabaseObject) + { + $testSqlDscIsDatabasePrincipalParameters = @{ + ServerObject = $ServerObject + DatabaseName = $DatabaseName + Name = $Name + ExcludeFixedRoles = $true + } + + $isDatabasePrincipal = Test-SqlDscIsDatabasePrincipal @testSqlDscIsDatabasePrincipalParameters + + if ($isDatabasePrincipal) + { + $getSqlDscDatabasePermissionResult = $sqlDatabaseObject.EnumDatabasePermissions($Name) + } + else + { + $missingPrincipalMessage = $script:localizedData.DatabasePermissionMissingPrincipal -f $Name, $DatabaseName + + if ($IgnoreMissingPrincipal.IsPresent) + { + Write-Verbose -Message $missingPrincipalMessage + } + else + { + throw $missingPrincipalMessage + } + } + } + else + { + $missingPrincipalMessage = $script:localizedData.DatabasePermissionMissingDatabase -f $DatabaseName + + if ($IgnoreMissingPrincipal.IsPresent) + { + Write-Verbose -Message $missingPrincipalMessage + } + else + { + throw $missingPrincipalMessage + } + } + + return , $getSqlDscDatabasePermissionResult +} +#EndRegion '.\Public\Get-SqlDscDatabasePermission.ps1' 103 +#Region '.\Public\Test-SqlDscIsDatabasePrincipal.ps1' 0 +<# + .SYNOPSIS + Returns whether the database principal exist. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER DatabaseName + Specifies the SQL database name. + + .PARAMETER Name + Specifies the name of the database principal. +#> +function Test-SqlDscIsDatabasePrincipal +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $DatabaseName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ExcludeUsers, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ExcludeRoles, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ExcludeFixedRoles, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ExcludeApplicationRoles + ) + + $principalExist = $false + + $sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName] + + if (-not $ExcludeUsers.IsPresent -and $sqlDatabaseObject.Users[$Name]) + { + $principalExist = $true + } + + if (-not $ExcludeRoles.IsPresent) + { + $userDefinedRole = if ($ExcludeFixedRoles.IsPresent) + { + # Skip fixed roles like db_datareader. + $sqlDatabaseObject.Roles | Where-Object -FilterScript { + -not $_.IsFixedRole -and $_.Name -eq $Name + } + } + else + { + $sqlDatabaseObject.Roles[$Name] + } + + if ($userDefinedRole) + { + $principalExist = $true + } + } + + if (-not $ExcludeApplicationRoles.IsPresent -and $sqlDatabaseObject.ApplicationRoles[$Name]) + { + $principalExist = $true + } + + return $principalExist +} +#EndRegion '.\Public\Test-SqlDscIsDatabasePrincipal.ps1' 85 diff --git a/source/Classes/002.DatabasePermission.ps1 b/source/Classes/002.DatabasePermission.ps1 index 15fe4cf46..aa46169c7 100644 --- a/source/Classes/002.DatabasePermission.ps1 +++ b/source/Classes/002.DatabasePermission.ps1 @@ -9,18 +9,17 @@ The permissions to be granted or denied for the user in the database. .NOTES - The parameter State cannot use the enum [Microsoft.SqlServer.Management.Smo.PermissionState] - because we cannot know what assembly to use prior to loading the SqlServerDsc. - A user can either choose SqlServer och SQLPS. When we can move to only SqlServer - then the the module SqlServer can me loaded in the SqlServerDsc's module - manifest and then we could possible use the type [Microsoft.SqlServer.Management.Smo.PermissionState] - directly. Then the parameter Permission could also be of the type - [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]. + The DSC properties specifies attributes Key and Required, those attributes + are not honored during compilation in the current implementation of + PowerShell DSC. They are kept here so when they do get honored it will help + detect missing properties during compilation. The Key property is evaluate + during runtime so that no two states are enforcing the same permission. #> class DatabasePermission { [DscProperty(Key)] - [DatabasePermissionState] + [ValidateSet('Grant', 'GrantWithGrant', 'Deny')] + [System.String] $State # TODO: Can we use a validate set for the permissions? diff --git a/source/Classes/003.SimpleResource.ps1 b/source/Classes/003.SimpleResource.ps1 deleted file mode 100644 index dc41fa372..000000000 --- a/source/Classes/003.SimpleResource.ps1 +++ /dev/null @@ -1,91 +0,0 @@ -<# - .SYNOPSIS - Resource for testing. -#> - -[DscResource()] -class SimpleResource -{ - [DscProperty(Key)] - [System.String] - $InstanceName - - [DscProperty(Key)] - [System.String] - $DatabaseName - - [DscProperty(Key)] - [System.String] - $Name - - [DscProperty()] - [System.String] - $ServerName = (Get-ComputerName) - - [DscProperty(Mandatory)] - [DatabasePermission[]] - $Permission - - [DscProperty()] - [Ensure] - $Ensure = [Ensure]::Present - - # [DscProperty(NotConfigurable)] - # [Reason[]] - # $Reasons - - [SimpleResource] Get() - { - $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) - - $dscResourceObject.InstanceName = 'SQL2017' - $dscResourceObject.DatabaseName = 'MyDB' - $dscResourceObject.Name = 'MyPrincipal' - $dscResourceObject.ServerName = 'MyHost' - $dscResourceObject.Ensure = 'Present' - - # Attempt 1 - <# - $dscResourceObject.Permission = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('CONNECT') - } - ) - #> - - # Attempt 2 - <# - $dscResourceObject.Permission = [CimInstance[]] @( - ( - New-CimInstance -ClientOnly -ClassName 'DatabasePermission' -Namespace 'root/microsoft/windows/desiredstateconfiguration' -Property @{ - State = 'Grant' - Permission = @('CONNECT') - } - ) - ) - #> - - # Attempt 3 - <# - $dscResourceObject.Permission = [DatabasePermission[]] @( - ( - New-CimInstance -ClientOnly -Namespace 'root/Microsoft/Windows/DesiredStateConfiguration' -ClassName 'DatabasePermission' -Property @{ - State = 'Grant' - Permission = @('CONNECT') - } - ) - ) - #> - return $dscResourceObject - } - - [System.Boolean] Test() - { - return $true - } - - [void] Set() - { - } -} diff --git a/source/Classes/001.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 similarity index 100% rename from source/Classes/001.ResourceBase.ps1 rename to source/Classes/010.ResourceBase.ps1 diff --git a/source/Classes/020.SimpleResource.ps1 b/source/Classes/020.SimpleResource.ps1 new file mode 100644 index 000000000..187a1378f --- /dev/null +++ b/source/Classes/020.SimpleResource.ps1 @@ -0,0 +1,68 @@ +<# + .SYNOPSIS + Resource for testing. +#> + +[DscResource()] +class SimpleResource +{ + [DscProperty(Key)] + [System.String] + $InstanceName + + [DscProperty(Key)] + [System.String] + $DatabaseName + + [DscProperty(Key)] + [System.String] + $Name + + [DscProperty()] + [System.String] + $ServerName = (Get-ComputerName) + + [DscProperty(Mandatory)] + [DatabasePermission[]] + $Permission + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + # [DscProperty(NotConfigurable)] + # [Reason[]] + # $Reasons + + [SimpleResource] Get() + { + $dscResourceObject = [SimpleResource] @{ + InstanceName = 'SQL2017' + DatabaseName = 'MyDB' + Name = 'MyPrincipal' + ServerName = 'MyHost' + Ensure = 'Present' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('CONNECT') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @('SELECT') + } + ) + } + + return $dscResourceObject + } + + [System.Boolean] Test() + { + return $true + } + + [void] Set() + { + } +} diff --git a/source/Classes/003.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 similarity index 90% rename from source/Classes/003.SqlDatabasePermission.ps1 rename to source/Classes/020.SqlDatabasePermission.ps1 index fc802ad11..89e47e4e5 100644 --- a/source/Classes/003.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -23,6 +23,21 @@ 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+SqlDatabasePermission). + ### Invalid values during compilation + + The parameter Permission is of type `[DatabasePermission]`. If a property + in the type is set to an invalid value an error will occur, this is expected. + This happens when the values are validated against the `[ValidateSet()]` + of the resource. In such case the following error will be thrown from + PowerShell DSC during the compilation of the configuration: + + ```plaintext + Failed to create an object of PowerShell class SqlDatabasePermission. + + CategoryInfo : InvalidOperation: (root/Microsoft/...gurationManager:String) [], CimException + + FullyQualifiedErrorId : InstantiatePSClassObjectFailed + + PSComputerName : localhost + ``` + .PARAMETER InstanceName The name of the SQL Server instance to be configured. Default value is 'MSSQLSERVER'. diff --git a/source/Enum/1.DatabasePermissionState.ps1 b/source/Enum/1.DatabasePermissionState.ps1 deleted file mode 100644 index 4a5e47efb..000000000 --- a/source/Enum/1.DatabasePermissionState.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -<# - .SYNOPSIS - The possible database permission states. - - .NOTES - This is the permission states from the enum [Microsoft.SqlServer.Management.Smo.PermissionState] - except for the 'Revoke' which is handled by a resource is using 'Absent' for - the parameter Ensure. -#> - -enum DatabasePermissionState -{ - Grant - Deny - GrantWithGrant -} From 72b8669a139ea6b5c5bc6282e880d05f7dbd9d94 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 1 Jul 2022 10:14:55 +0200 Subject: [PATCH 14/98] Fix code --- source/Classes/020.SimpleResource.ps1 | 14 ++++++++++---- source/en-US/SimpleResource.strings.psd1 | 8 ++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 source/en-US/SimpleResource.strings.psd1 diff --git a/source/Classes/020.SimpleResource.ps1 b/source/Classes/020.SimpleResource.ps1 index 187a1378f..20d1256b4 100644 --- a/source/Classes/020.SimpleResource.ps1 +++ b/source/Classes/020.SimpleResource.ps1 @@ -4,7 +4,7 @@ #> [DscResource()] -class SimpleResource +class SimpleResource : ResourceBase { [DscProperty(Key)] [System.String] @@ -30,9 +30,9 @@ class SimpleResource [Ensure] $Ensure = [Ensure]::Present - # [DscProperty(NotConfigurable)] - # [Reason[]] - # $Reasons + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons [SimpleResource] Get() { @@ -52,6 +52,12 @@ class SimpleResource Permission = @('SELECT') } ) + Reasons = [Reason[]] @( + [Reason] @{ + Code = '{0}:{0}:Ensure' -f $this.GetType() + Phrase = 'The property Ensure should be Present, but was Absent' + } + ) } return $dscResourceObject diff --git a/source/en-US/SimpleResource.strings.psd1 b/source/en-US/SimpleResource.strings.psd1 new file mode 100644 index 000000000..13d2848dd --- /dev/null +++ b/source/en-US/SimpleResource.strings.psd1 @@ -0,0 +1,8 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource SimpleResource. +#> + +ConvertFrom-StringData @' +'@ From f1d4855d889a476fc69931a8ab2a3b13f20d879b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 1 Jul 2022 21:56:17 +0200 Subject: [PATCH 15/98] Fix code changes --- source/Classes/020.SqlDatabasePermission.ps1 | 22 ++++++++++++++++++- .../SqlServerDsc.Common.psm1 | 10 ++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 89e47e4e5..d597bd694 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -23,6 +23,12 @@ 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+SqlDatabasePermission). + ### `PSDscRunAsCredential` not supported + + The built-in property `PSDscRunAsCredential` does not work with class-based + resources that using advanced type like the parameter `Permission` does. + Use the parameter `Credential` instead of `PSDscRunAsCredential`. + ### Invalid values during compilation The parameter Permission is of type `[DatabasePermission]`. If a property @@ -88,6 +94,10 @@ class SqlDatabasePermission : ResourceBase [DatabasePermission[]] $Permission + [DscProperty()] + [PSCredential] + $Credential + [DscProperty()] [Ensure] $Ensure = [Ensure]::Present @@ -129,7 +139,17 @@ class SqlDatabasePermission : ResourceBase Name = $properties.Name } - $sqlServerObject = Connect-SqlDscDatabaseEngine -ServerName $this.ServerName -InstanceName $properties.InstanceName + $connectSqlDscDatabaseEngineParameters = @{ + ServerName = $this.ServerName + InstanceName = $properties.InstanceName + } + + if ($this.Credential) + { + $connectSqlDscDatabaseEngineParameters.Credential = $this.Credential + } + + $sqlServerObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters # TA BORT -VERBOSE! Write-Verbose -Verbose -Message ( diff --git a/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 b/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 index d69b74806..d170b445d 100644 --- a/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 +++ b/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 @@ -481,9 +481,9 @@ function Connect-SQL [Parameter(ParameterSetName = 'SqlServerWithCredential', Mandatory = $true)] [ValidateNotNull()] - [Alias('DatabaseCredential')] + [Alias('SetupCredential', 'DatabaseCredential')] [System.Management.Automation.PSCredential] - $SetupCredential, + $Credential, [Parameter(ParameterSetName = 'SqlServerWithCredential')] [ValidateSet('WindowsUser', 'SqlLogin')] @@ -527,7 +527,7 @@ function Connect-SQL } else { - $connectUserName = $SetupCredential.UserName + $connectUserName = $Credential.UserName Write-Verbose -Message ( $script:localizedData.ConnectingUsingImpersonation -f $connectUsername, $LoginType @@ -537,7 +537,7 @@ function Connect-SQL { $sqlConnectionContext.LoginSecure = $false $sqlConnectionContext.Login = $connectUserName - $sqlConnectionContext.SecurePassword = $SetupCredential.Password + $sqlConnectionContext.SecurePassword = $Credential.Password } if ($LoginType -eq 'WindowsUser') @@ -545,7 +545,7 @@ function Connect-SQL $sqlConnectionContext.LoginSecure = $true $sqlConnectionContext.ConnectAsUser = $true $sqlConnectionContext.ConnectAsUserName = $connectUserName - $sqlConnectionContext.ConnectAsUserPassword = $SetupCredential.GetNetworkCredential().Password + $sqlConnectionContext.ConnectAsUserPassword = $Credential.GetNetworkCredential().Password } } From 9ba5db2609e049ce8a08cb3a1b688c61d21177c5 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 1 Jul 2022 21:59:24 +0200 Subject: [PATCH 16/98] Fix comment --- source/Classes/010.ResourceBase.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index 16f874afa..92157db58 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -35,6 +35,10 @@ class ResourceBase TODO: Should call back to the derived class for proper handling of adding additional parameters to the variable $keyProperty that needs to be passed to GetCurrentState(). + + Second though, might not be necessary as the override for GetCurrentState + can call $this. to get any non-key properties. + It might even be that we don't need Get-KeyProperty? #> #$specialKeyProperty = @() From 9b695d4cc1e081fd3592928e5d650ed9e6a6285d Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 1 Jul 2022 22:01:59 +0200 Subject: [PATCH 17/98] Fix comment --- source/Classes/020.SqlDatabasePermission.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index d597bd694..8d679667b 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -71,6 +71,8 @@ Returns the reason a property is not in desired state. #> +# TODO: Add this if PsDscRunAsCredential is not supported. +#[DscResource(RunAsCredential = 'NotSupported')] [DscResource()] class SqlDatabasePermission : ResourceBase { From 9f6ce99b0be52dc980356f8ac4f09a64fc664742 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 2 Jul 2022 16:51:20 +0200 Subject: [PATCH 18/98] Fix code --- .vscode/analyzersettings.psd1 | 7 +- azure-pipelines.yml | 2 +- source/Classes/020.SimpleResource.ps1 | 1 + source/Classes/020.SqlDatabasePermission.ps1 | 3 + .../1-GrantDatabasePermissions.ps1 | 69 +++++++++++-------- .../2-RevokeDatabasePermissions.ps1 | 46 ++++++++----- .../3-DenyDatabasePermissions.ps1 | 30 ++++++-- .../4-InvokeClassResource.ps1 | 19 ----- source/Private/Get-ClassName.ps1 | 2 +- tests/QA/ScriptAnalyzer.Tests.ps1 | 10 +++ 10 files changed, 116 insertions(+), 73 deletions(-) delete mode 100644 source/Examples/Resources/SqlDatabasePermission/4-InvokeClassResource.ps1 diff --git a/.vscode/analyzersettings.psd1 b/.vscode/analyzersettings.psd1 index f7c2f4486..b73f0d1a7 100644 --- a/.vscode/analyzersettings.psd1 +++ b/.vscode/analyzersettings.psd1 @@ -70,6 +70,11 @@ 'UseSyntacticallyCorrectExamples' ) + # TODO: This is not excluded correctly, see test QA/ScriptAnalyzer.Tests.ps1 for more information. + ExcludeRules = @( + 'TypeNotFound' + ) + Rules = @{ PSUseConsistentWhitespace = @{ Enable = $true @@ -80,7 +85,7 @@ CheckSeparator = $true CheckPipe = $true CheckPipeForRedundantWhitespace = $true - CheckParameter = $true + CheckParameter = $false } PSPlaceOpenBrace = @{ diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 29f821f4d..35c54c6a7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -68,7 +68,7 @@ stages: buildType: 'current' artifactName: $(buildArtifactName) targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' - - pwsh: | + - powershell: | # Workaround for issue https://github.com/dsccommunity/DscResource.Test/issues/100 ./build.ps1 -Task noop diff --git a/source/Classes/020.SimpleResource.ps1 b/source/Classes/020.SimpleResource.ps1 index 20d1256b4..1c340b920 100644 --- a/source/Classes/020.SimpleResource.ps1 +++ b/source/Classes/020.SimpleResource.ps1 @@ -70,5 +70,6 @@ class SimpleResource : ResourceBase [void] Set() { + Write-Verbose -Verbose -Message 'NotImplemented: Set()' } } diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 8d679667b..ef369141f 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -235,6 +235,9 @@ class SqlDatabasePermission : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { + # TODO: Add the evaluation so that one permission can't have two different states. + Write-Verbose -Verbose -Message 'NotImplemented: AssertProperties()' + # @( # 'DirectoryPartitionAutoEnlistInterval', # 'TombstoneInterval' diff --git a/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 index 78cf1488c..424b9a914 100644 --- a/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 +++ b/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 @@ -19,41 +19,56 @@ Configuration Example { SqlDatabasePermission 'Grant_SqlDatabasePermissions_SQLAdmin_Db01' { - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorks' - PermissionState = 'Grant' - Permissions = @('Connect', 'Update') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + DatabaseName = 'AdventureWorks' + Permission = [CimInstance[]] @( + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'Grant' + Permission = @('Connect', 'Update') + } + ) + ) + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Credential = $SqlAdministratorCredential } SqlDatabasePermission 'Grant_SqlDatabasePermissions_SQLUser_Db01' { - Ensure = 'Present' - Name = 'CONTOSO\SQLUser' - DatabaseName = 'AdventureWorks' - PermissionState = 'Grant' - Permissions = @('Connect', 'Update') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + Name = 'CONTOSO\SQLUser' + DatabaseName = 'AdventureWorks' + Permission = [CimInstance[]] @( + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'Grant' + Permission = @('Connect', 'Update') + } + ) + ) + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Credential = $SqlAdministratorCredential } SqlDatabasePermission 'Grant_SqlDatabasePermissions_SQLAdmin_Db02' { - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorksLT' - PermissionState = 'Grant' - Permissions = @('Connect', 'Update') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + DatabaseName = 'AdventureWorksLT' + Permission = [CimInstance[]] @( + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'Grant' + Permission = @('Connect', 'Update') + } + ) + ) + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Credential = $SqlAdministratorCredential } } } diff --git a/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 index 888e2e887..d174b220c 100644 --- a/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 +++ b/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 @@ -19,28 +19,38 @@ Configuration Example { SqlDatabasePermission 'RevokeGrant_SqlDatabasePermissions_SQLAdmin' { - Ensure = 'Absent' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorks' - PermissionState = 'Grant' - Permissions = @('Connect', 'Update') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Absent' + Name = 'CONTOSO\SQLAdmin' + DatabaseName = 'AdventureWorks' + Permission = [CimInstance[]] @( + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'Grant' + Permission = @('Connect', 'Update') + } + ) + ) + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Credential = $SqlAdministratorCredential } SqlDatabasePermission 'RevokeDeny_SqlDatabasePermissions_SQLAdmin' { - Ensure = 'Absent' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorks' - PermissionState = 'Deny' - Permissions = @('Select', 'CreateTable') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Absent' + Name = 'CONTOSO\SQLAdmin' + DatabaseName = 'AdventureWorks' + Permission = [CimInstance[]] @( + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'Deny' + Permission = @('Select', 'CreateTable') + } + ) + ) + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Credential = $SqlAdministratorCredential } } } diff --git a/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 index 6eaddb016..390332480 100644 --- a/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 +++ b/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 @@ -22,8 +22,14 @@ Configuration Example Ensure = 'Present' Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorks' - PermissionState = 'Deny' - Permissions = @('Select', 'CreateTable') + Permission = [CimInstance[]] @( + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'Deny' + Permission = @('Select', 'CreateTable') + } + ) + ) ServerName = 'sqltest.company.local' InstanceName = 'DSC' @@ -35,8 +41,14 @@ Configuration Example Ensure = 'Present' Name = 'CONTOSO\SQLUser' DatabaseName = 'AdventureWorks' - PermissionState = 'Deny' - Permissions = @('Select', 'CreateTable') + Permission = [CimInstance[]] @( + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'Deny' + Permission = @('Select', 'CreateTable') + } + ) + ) ServerName = 'sqltest.company.local' InstanceName = 'DSC' @@ -48,8 +60,14 @@ Configuration Example Ensure = 'Present' Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorksLT' - PermissionState = 'Deny' - Permissions = @('Select', 'CreateTable') + Permission = [CimInstance[]] @( + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'Deny' + Permission = @('Select', 'CreateTable') + } + ) + ) ServerName = 'sqltest.company.local' InstanceName = 'DSC' diff --git a/source/Examples/Resources/SqlDatabasePermission/4-InvokeClassResource.ps1 b/source/Examples/Resources/SqlDatabasePermission/4-InvokeClassResource.ps1 deleted file mode 100644 index 0ead3fcfe..000000000 --- a/source/Examples/Resources/SqlDatabasePermission/4-InvokeClassResource.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -# $a = Invoke-DscResource -ModuleName SqlServerDsc -Name SqlDatabasePermission -Method Get -Property @{ -# Ensure = 'Present' -# ServerName = 'localhost' -# InstanceName = 'sql2017' -# DatabaseName = 'AdventureWorks2' -# Name = 'SQLTEST\sqluser' -# Permission = [CimInstance[]]@( -# (New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ -# State = 'Grant' -# Permission = @('select') -# }) -# (New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ -# State = 'GrantWithGrant' -# Permission = @('update') -# }) -# ) - -# PSDscRunAsCredential = $SqlInstallCredential -# } -Verbose diff --git a/source/Private/Get-ClassName.ps1 b/source/Private/Get-ClassName.ps1 index a025d1f86..648db7fdc 100644 --- a/source/Private/Get-ClassName.ps1 +++ b/source/Private/Get-ClassName.ps1 @@ -39,5 +39,5 @@ function Get-ClassName } } - return ,$class + return , $class } diff --git a/tests/QA/ScriptAnalyzer.Tests.ps1 b/tests/QA/ScriptAnalyzer.Tests.ps1 index 136c2c1ba..cc5b43f4e 100644 --- a/tests/QA/ScriptAnalyzer.Tests.ps1 +++ b/tests/QA/ScriptAnalyzer.Tests.ps1 @@ -50,6 +50,16 @@ Describe 'Script Analyzer Rules' { It 'Should pass all PS Script Analyzer rules for file ''''' -ForEach $testCases { $pssaError = Invoke-ScriptAnalyzer -Path $ScriptPath -Settings $scriptAnalyzerSettingsPath + <# + Filter out rule TypeNotFound. + + TODO: The rule "TypeNotFound" is not excluded correctly even if it is + excluded in the file 'analyzersettings.psd1'. This is a workaround + until it is properly excluded for source files, and instead only + ran for the built module script module file (SqlServerDsc.psm1). + #> + $pssaError = $pssaError | Where-Object -FilterScript { $_.RuleName -ne 'TypeNotFound'} + $report = $pssaError | Format-Table -AutoSize | Out-String -Width 200 $pssaError | Should -HaveCount 0 -Because "all script analyzer rules should pass.`r`n`r`n $report`r`n" } From 90df25e43766c5e3e0a2cdabbb9cf8a46f883407 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 2 Jul 2022 18:00:04 +0200 Subject: [PATCH 19/98] Update CHANGELOG.md --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b6100b68..9ee9aac0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,6 +128,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - BREAKING CHANGE: The parameter `IsHadrEnabled` is no longer returned by `Get-TargetResource`. The `Ensure` parameter now returns `Present` if Always On HADR is enabled and `Absent` if it is disabled. +- SqlDatabasePermission + - BREAKING CHANGE: The parameter `ParameterState` has been removed and + the parameter `Permission` is now an instance of the type `DatabasePermission`. + The type `DatabasePermission` contains two properties; `State` and + `Permission`. + - Refactored resource as a class-based resource. ### Fixed @@ -200,6 +206,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 step for each iteration. A line of code was moved outside of the loop. - The `SourcePath` parameter is now mandatory for all `*-TargetResource` ([issue #1755](https://github.com/dsccommunity/SqlServerDsc/issues/1755)). +- SqlDatabasePermission + - It is no longer possible to have one permission that has two different + states in the same configuration, e.g. denying and granting `update` + in the same configuration. ## [15.2.0] - 2021-09-01 From 6f8f9340c2caa798729252079b41f9e122e7b6a6 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 2 Jul 2022 18:00:26 +0200 Subject: [PATCH 20/98] Fix comment in azure-pipelines.yml --- azure-pipelines.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 35c54c6a7..dc2f4149b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -68,6 +68,10 @@ stages: buildType: 'current' artifactName: $(buildArtifactName) targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + # This task need to user Windows PowerShell due to a bug in PS7 that cannot + # find/use class-based DSC resources that uses inheritance, which result in + # the examples cannot compile. See the following issue for more information: + # https://github.com/dsccommunity/DnsServerDsc/issues/268#issuecomment-918505230 - powershell: | # Workaround for issue https://github.com/dsccommunity/DscResource.Test/issues/100 ./build.ps1 -Task noop @@ -78,7 +82,7 @@ stages: $pesterConfig.Output.Verbosity = 'Detailed' Invoke-Pester -Configuration $pesterConfig - name: qualitytest + name: qualityTest displayName: 'Run SqlServerDsc QA Test' - task: PowerShell@2 name: test From a9d04b7413ea3c53044637047fabd2cc5e0292f0 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 2 Jul 2022 18:36:56 +0200 Subject: [PATCH 21/98] Fix unit tests --- .../Unit/Classes/DatabasePermission.Tests.ps1 | 2 +- .../Classes/SqlDatabasePermission.Tests.ps1 | 92 ++++++++++++++++++- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Classes/DatabasePermission.Tests.ps1 b/tests/Unit/Classes/DatabasePermission.Tests.ps1 index b68fa89c3..55b15c3ca 100644 --- a/tests/Unit/Classes/DatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/DatabasePermission.Tests.ps1 @@ -61,7 +61,7 @@ Describe 'DatabasePermission' -Tag 'DatabasePermission' { $script:mockDatabasePermissionInstance = InModuleScope -ScriptBlock { $databasPermissionInstance = [DatabasePermission]::new() - $databasPermissionInstance.State = [DatabasePermissionState]::Grant + $databasPermissionInstance.State = 'Grant' $databasPermissionInstance.Permission = 'select' return $databasPermissionInstance diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index d8ddd466d..3997ea4e9 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -87,10 +87,10 @@ Describe 'SqlDatabasePermission' { } Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { - BeforeEach { + BeforeAll { InModuleScope -ScriptBlock { $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ - Name = 'MockUserName' + Name = 'MockUserName' DatabaseName = 'MockDatabaseName' InstanceName = 'NamedInstance' } @@ -99,11 +99,97 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { Context 'When the system is in the desired state' { Context 'When the desired permission does exist' { - It 'Should return the state as absent' { + BeforeAll { + InModuleScope -ScriptBlock { + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Ensure = 'Present' + InstanceName = 'NamedInstance' + DatabaseName = 'MockDatabaseName' + Name = 'MockUserName' + ServerName = 'localhost' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + ) + Credential = $null + Reasons = $null + } + } + } + } + + It 'Should return the state as present' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.Get() + + $currentState.Ensure | Should -Be 'Present' + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.DatabaseName | Should -Be 'MockDatabaseName' + $currentState.Name | Should -Be 'MockUserName' + $currentState.ServerName | Should -Be 'localhost' + $currentState.Credential | Should -BeNullOrEmpty + $currentState.Reasons | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + + $currentState.Permission[0].State | Should -Be 'Grant' + $currentState.Permission[0].Permission | Should -Be 'Connect' + } + } + } + + Context 'When the desired permission does not exist' { + BeforeAll { + InModuleScope -ScriptBlock { + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Ensure = 'Absent' + InstanceName = 'NamedInstance' + DatabaseName = 'MockDatabaseName' + Name = 'MockUserName' + ServerName = 'localhost' + Permission = [DatabasePermission[]] @() + Credential = $null + Reasons = $null + } + } + } + } + + It 'Should return the state as present' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.Get() $currentState.Ensure | Should -Be 'Absent' + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.DatabaseName | Should -Be 'MockDatabaseName' + $currentState.Name | Should -Be 'MockUserName' + $currentState.ServerName | Should -Be 'localhost' + $currentState.Credential | Should -BeNullOrEmpty + $currentState.Reasons | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + + $currentState.Permission| Should -BeNullOrEmpty } } } From 49f88b63d6d0162d490b1244bc0ef1ffb439ae57 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 3 Jul 2022 20:52:20 +0200 Subject: [PATCH 22/98] Fix code --- debug.ps1 | 1125 ----------------- source/Classes/010.ResourceBase.ps1 | 62 +- source/Classes/020.SqlDatabasePermission.ps1 | 52 +- .../Test-ResourceHasEnsureProperty.ps1 | 38 + 4 files changed, 140 insertions(+), 1137 deletions(-) delete mode 100644 debug.ps1 create mode 100644 source/Private/Test-ResourceHasEnsureProperty.ps1 diff --git a/debug.ps1 b/debug.ps1 deleted file mode 100644 index d4e18f280..000000000 --- a/debug.ps1 +++ /dev/null @@ -1,1125 +0,0 @@ -#Region '.\prefix.ps1' 0 -$script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/DscResource.Common' -Import-Module -Name $script:dscResourceCommonModulePath - -# TODO: The goal would be to remove this, when no classes and public or private functions need it. -$script:sqlServerDscCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/SqlServerDsc.Common' -Import-Module -Name $script:sqlServerDscCommonModulePath - -$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' -#EndRegion '.\prefix.ps1' 9 -#Region '.\Enum\1.Ensure.ps1' 0 -<# - .SYNOPSIS - The possible states for the DSC resource parameter Ensure. -#> - -enum Ensure -{ - Present - Absent -} -#EndRegion '.\Enum\1.Ensure.ps1' 11 - -#Region '.\Classes\002.DatabasePermission.ps1' 0 -<# - .SYNOPSIS - The possible database permission states. - - .PARAMETER State - The state of the permission. - - .PARAMETER Permission - The permissions to be granted or denied for the user in the database. - - .NOTES - The DSC properties specifies attributes Key and Required, those attributes - are not honored during compilation in the current implementation of - PowerShell DSC. They are kept here so when they do get honored it will help - detect missing properties during compilation. The Key property is evaluate - during runtime so that no two states are enforcing the same permission. -#> -class DatabasePermission -{ - [DscProperty(Key)] - [ValidateSet('Grant', 'GrantWithGrant', 'Deny')] - [System.String] - $State - - # TODO: Can we use a validate set for the permissions? - [DscProperty(Mandatory)] - [System.String[]] - $Permission -} -#EndRegion '.\Classes\002.DatabasePermission.ps1' 30 -#Region '.\Classes\002.Reason.ps1' 0 -class Reason -{ - [DscProperty()] - [System.String] - $Code - - [DscProperty()] - [System.String] - $Phrase -} -#EndRegion '.\Classes\002.Reason.ps1' 11 -#Region '.\Classes\003.SimpleResource.ps1' 0 -<# - .SYNOPSIS - Resource for testing. -#> - -[DscResource()] -class SimpleResource -{ - [DscProperty(Key)] - [System.String] - $InstanceName - - [DscProperty(Key)] - [System.String] - $DatabaseName - - [DscProperty(Key)] - [System.String] - $Name - - [DscProperty()] - [System.String] - $ServerName = (Get-ComputerName) - - [DscProperty(Mandatory)] - [DatabasePermission[]] - $Permission - - [DscProperty()] - [Ensure] - $Ensure = [Ensure]::Present - - [DscProperty(NotConfigurable)] - [Reason[]] - $Reasons - - [SimpleResource] Get() - { - $dscResourceObject = [SimpleResource] @{ - InstanceName = 'SQL2017' - DatabaseName = 'MyDB' - Name = 'MyPrincipal' - ServerName = 'MyHost' - Ensure = 'Present' - Permission = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('CONNECT') - } - [DatabasePermission] @{ - State = 'Deny' - Permission = @('SELECT') - } - ) - Reasons = [Reason[]] @( - [Reason] @{ - Code = '{0}:{0}:Ensure' -f $this.GetType() - Phrase = 'The property Ensure should be Present, but was Absent' - } - ) - } - - return $dscResourceObject - } - - [System.Boolean] Test() - { - return $true - } - - [void] Set() - { - } -} -#EndRegion '.\Classes\003.SimpleResource.ps1' 69 -#Region '.\Classes\001.ResourceBase.ps1' 0 -<# - .SYNOPSIS - A class with methods that are equal for all class-based resources. - - .DESCRIPTION - A class with methods that are equal for all class-based resources. - - .NOTES - This class should not contain any DSC properties. -#> - -class ResourceBase -{ - # Hidden property for holding localization strings - hidden [System.Collections.Hashtable] $localizedData = @{} - - # Default constructor - ResourceBase() - { - # TODO: When this fails the LCM returns 'Failed to create an object of PowerShell class SqlDatabasePermission' instead of the actual error that occurred. - $this.localizedData = Get-LocalizedDataRecursive -ClassName ($this | Get-ClassName -Recurse) - } - - [ResourceBase] Get() - { - $this.Assert() - - # Get all key properties. - $keyProperty = $this | Get-KeyProperty - - # TODO: TA BORT -VERBOSE - Write-Verbose -Verbose -Message ($this.localizedData.GetCurrentState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) - - <# - TODO: Should call back to the derived class for proper handling of adding - additional parameters to the variable $keyProperty that needs to be - passed to GetCurrentState(). - #> - #$specialKeyProperty = @() - - $getCurrentStateResult = $this.GetCurrentState($keyProperty) - - Write-Verbose -Verbose -Message ($getCurrentStateResult | Out-String) - - $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) - - - $dscResourceObject.InstanceName = 'SQL2017' - $dscResourceObject.DatabaseName = 'MyDB' - $dscResourceObject.Name = 'MyPrincipal' - $dscResourceObject.ServerName = 'MyHost' - $dscResourceObject.Ensure = 'Present' - $dscResourceObject.Permission = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('CONNECT') - } - [DatabasePermission] @{ - State = 'Deny' - Permission = @('SELECT') - } - ) - $dscResourceObject.Reasons = [Reason[]] @( - [Reason] @{ - Code = '{0}:{0}:Ensure' -f $this.GetType() - Phrase = 'The property Ensure should be Present, but was Absent' - } - ) - - - return $dscResourceObject - - $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) - - foreach ($propertyName in $this.PSObject.Properties.Name) - { - Write-Verbose -Verbose -Message '----------------------------' - Write-Verbose -Verbose -Message $propertyName - - if ($propertyName -in @($getCurrentStateResult.Keys)) - { - Write-verbose -Verbose -Message ($getCurrentStateResult.$propertyName.GetType().FullName | Out-String) - Write-verbose -Verbose -Message ($getCurrentStateResult.$propertyName.GetType().IsArray | Out-String) - Write-verbose -Verbose -Message ($getCurrentStateResult.$propertyName.GetType().Module.ScopeName | Out-String) - #Write-verbose -Verbose -Message ($getCurrentStateResult.$propertyName.GetType() | fl * | Out-String) - - if ($propertyName -eq 'Permission') - { - Write-verbose -Verbose -Message 'HARDCODE PERMISSION' - - $dscResourceObject.Permission = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('CONNECT') - } - [DatabasePermission] @{ - State = 'Deny' - Permission = @('SELECT') - } - ) - } - else - { - $dscResourceObject.$propertyName = $getCurrentStateResult.$propertyName -as $getCurrentStateResult.$propertyName.GetType().FullName - } - } - } - - # Return properties. - return $dscResourceObject - } - - [void] Set() - { - # Get all key properties. - $keyProperty = $this | Get-KeyProperty - - Write-Verbose -Verbose -Message ($this.localizedData.SetDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) - - $this.Assert() - - # Call the Compare method to get enforced properties that are not in desired state. - $propertiesNotInDesiredState = $this.Compare() - - if ($propertiesNotInDesiredState) - { - $propertiesToModify = $this.GetDesiredStateForSplatting($propertiesNotInDesiredState) - - $propertiesToModify.Keys | ForEach-Object -Process { - Write-Verbose -Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_) - } - - <# - Call the Modify() method with the properties that should be enforced - and was not in desired state. - #> - $this.Modify($propertiesToModify) - } - else - { - Write-Verbose -Verbose -Message $this.localizedData.NoPropertiesToSet - } - } - - [System.Boolean] Test() - { - # Get all key properties. - $keyProperty = $this | Get-KeyProperty - - Write-Verbose -Verbose -Message ($this.localizedData.TestDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) - - $this.Assert() - - $isInDesiredState = $true - - <# - Returns all enforced properties not in desires state, or $null if - all enforced properties are in desired state. - #> - $propertiesNotInDesiredState = $this.Compare() - - if ($propertiesNotInDesiredState) - { - $isInDesiredState = $false - } - - if ($isInDesiredState) - { - Write-Verbose -Verbose -Message $this.localizedData.InDesiredState - } - else - { - Write-Verbose -Verbose -Message $this.localizedData.NotInDesiredState - } - - return $isInDesiredState - } - - <# - Returns a hashtable containing all properties that should be enforced and - are not in desired state. - - This method should normally not be overridden. - #> - hidden [System.Collections.Hashtable[]] Compare() - { - $currentState = $this.Get() | ConvertFrom-DscResourceInstance - $desiredState = $this | Get-DesiredStateProperty - - $CompareDscParameterState = @{ - CurrentValues = $currentState - DesiredValues = $desiredState - Properties = $desiredState.Keys - ExcludeProperties = @('DnsServer') - IncludeValue = $true - } - - <# - Returns all enforced properties not in desires state, or $null if - all enforced properties are in desired state. - #> - return (Compare-DscParameterState @CompareDscParameterState) - } - - # Returns a hashtable containing all properties that should be enforced. - hidden [System.Collections.Hashtable] GetDesiredStateForSplatting([System.Collections.Hashtable[]] $Properties) - { - $desiredState = @{} - - $Properties | ForEach-Object -Process { - $desiredState[$_.Property] = $_.ExpectedValue - } - - return $desiredState - } - - # This method should normally not be overridden. - hidden [void] Assert() - { - $desiredState = $this | Get-DesiredStateProperty - - $this.AssertProperties($desiredState) - } - - <# - This method can be overridden if resource specific property asserts are - needed. The parameter properties will contain the properties that was - passed a value. - #> - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidEmptyNamedBlocks', '')] - hidden [void] AssertProperties([System.Collections.Hashtable] $properties) - { - } - - <# - This method must be overridden by a resource. The parameter properties will - contain the properties that should be enforced and that are not in desired - state. - #> - hidden [void] Modify([System.Collections.Hashtable] $properties) - { - throw $this.localizedData.ModifyMethodNotImplemented - } - - <# - This method must be overridden by a resource. The parameter properties will - contain the key properties. - #> - hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) - { - throw $this.localizedData.GetCurrentStateMethodNotImplemented - } -} -#EndRegion '.\Classes\001.ResourceBase.ps1' 200 - -#Region '.\Classes\003.SqlDatabasePermission.ps1' 0 -<# - .SYNOPSIS - The `SqlDatabasePermission` DSC resource is used to grant, deny or revoke - permissions for a user in a database - - .DESCRIPTION - The `SqlDatabasePermission` DSC resource is used to grant, deny or revoke - permissions for a user in a database. For more information about permissions, - please read the article [Permissions (Database Engine)](https://docs.microsoft.com/en-us/sql/relational-databases/security/permissions-database-engine). - - >**Note:** When revoking permission with PermissionState 'GrantWithGrant', both the - >grantee and _all the other users the grantee has granted the same permission to_, - >will also get their permission revoked. - - Valid permission names can be found in the article [DatabasePermissionSet Class properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.databasepermissionset#properties). - - ## Requirements - - * Target machine must be running Windows Server 2012 or later. - * Target machine must be running SQL Server Database Engine 2012 or later. - - ## 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+SqlDatabasePermission). - - .PARAMETER InstanceName - The name of the SQL Server instance to be configured. Default value is - 'MSSQLSERVER'. - - .PARAMETER DatabaseName - The name of the database. - - .PARAMETER Name - The name of the user that should be granted or denied the permission. - - .PARAMETER ServerName - The host name of the SQL Server to be configured. Default value is the - current computer name. - - .PARAMETER Permission - An array of database permissions to enforce. - - This is an array of CIM instances of class `DatabasePermission` from the - namespace `root/Microsoft/Windows/DesiredStateConfiguration`. - - .PARAMETER Ensure - If the permission should be granted ('Present') or revoked ('Absent'). - - .PARAMETER Reasons - Returns the reason a property is not in desired state. -#> - -[DscResource()] -class SqlDatabasePermission : ResourceBase -{ - [DscProperty(Key)] - [System.String] - $InstanceName - - [DscProperty(Key)] - [System.String] - $DatabaseName - - [DscProperty(Key)] - [System.String] - $Name - - [DscProperty()] - [System.String] - $ServerName = (Get-ComputerName) - - [DscProperty(Mandatory)] - [DatabasePermission[]] - $Permission - - [DscProperty()] - [Ensure] - $Ensure = [Ensure]::Present - - [DscProperty(NotConfigurable)] - [Reason[]] - $Reasons - - [SqlDatabasePermission] Get() - { - $dscResourceObject = [SimpleResource] @{ - InstanceName = 'SQL2017' - DatabaseName = 'MyDB' - Name = 'MyPrincipal' - ServerName = 'MyHost' - Ensure = 'Present' - Permission = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('CONNECT') - } - [DatabasePermission] @{ - State = 'Deny' - Permission = @('SELECT') - } - ) - Reasons = [Reason[]] @( - [Reason] @{ - Code = '{0}:{0}:Ensure' -f $this.GetType() - Phrase = 'The property Ensure should be Present, but was Absent' - } - ) - } - - return $dscResourceObject - - # Call the base method to return the properties. - return ([ResourceBase] $this).Get() - } - - [System.Boolean] Test() - { - # Call the base method to test all of the properties that should be enforced. - return ([ResourceBase] $this).Test() - } - - [void] Set() - { - # Call the base method to enforce the properties. - ([ResourceBase] $this).Set() - } - - <# - Base method Get() call this method to get the current state as a hashtable. - The parameter properties will contain the key properties. - #> - hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) - { - $currentState = @{ - Ensure = 'Absent' - ServerName = $this.ServerName - InstanceName = $properties.InstanceName - DatabaseName = $properties.DatabaseName - Permission = [DatabasePermission[]] @() - Name = $properties.Name - } - - $sqlServerObject = Connect-SqlDscDatabaseEngine -ServerName $this.ServerName -InstanceName $properties.InstanceName - - # TA BORT -VERBOSE! - Write-Verbose -Verbose -Message ( - $this.localizedData.EvaluateDatabasePermissionForPrincipal -f @( - $properties.Name, - $properties.DatabaseName, - $properties.InstanceName - ) - ) - - $databasePermissionInfo = $sqlServerObject | - Get-SqlDscDatabasePermission -DatabaseName $this.DatabaseName -Name $this.Name -IgnoreMissingPrincipal - - if ($databasePermissionInfo) - { - $permissionState = $databasePermissionInfo | ForEach-Object -Process { - # Convert from the type PermissionState to String. - [System.String] $_.PermissionState - } | Select-Object -Unique - - foreach ($currentPermissionState in $permissionState) - { - $filteredDatabasePermission = $databasePermissionInfo | - Where-Object -FilterScript { - $_.PermissionState -eq $currentPermissionState - } - - $databasePermission = [DatabasePermission] @{ - State = $currentPermissionState - } - - # Initialize variable permission - [System.String[]] $statePermissionResult = @() - - foreach ($currentPermission in $filteredDatabasePermission) - { - $permissionProperty = ( - $currentPermission.PermissionType | - Get-Member -MemberType Property - ).Name - - foreach ($currentPermissionProperty in $permissionProperty) - { - if ($currentPermission.PermissionType."$currentPermissionProperty") - { - $statePermissionResult += $currentPermissionProperty - } - } - } - - <# - Sort and remove any duplicate permissions, also make sure - it is an array even if only one item. - #> - $databasePermission.Permission = @( - $statePermissionResult | - Sort-Object -Unique - ) - - [DatabasePermission[]] $currentState.Permission += $databasePermission - } - } - - return $currentState - } - - <# - Base method Set() call this method with the properties that should be - enforced and that are not in desired state. - #> - hidden [void] Modify([System.Collections.Hashtable] $properties) - { - Write-Verbose -Message ($properties | Out-String) -Verbose - - #Set-DnsServerDsSetting @properties - } - - <# - Base method Assert() call this method with the properties that was passed - a value. - #> - hidden [void] AssertProperties([System.Collections.Hashtable] $properties) - { - # @( - # 'DirectoryPartitionAutoEnlistInterval', - # 'TombstoneInterval' - # ) | ForEach-Object -Process { - # $valueToConvert = $this.$_ - - # # Only evaluate properties that have a value. - # if ($null -ne $valueToConvert) - # { - # Assert-TimeSpan -PropertyName $_ -Value $valueToConvert -Minimum '0.00:00:00' - # } - # } - } -} -#EndRegion '.\Classes\003.SqlDatabasePermission.ps1' 215 -#Region '.\Private\Get-ClassName.ps1' 0 -<# - .SYNOPSIS - Get the class name of the passed object, and optional an array with - all inherited classes. - - .PARAMETER InputObject - The object to be evaluated. - - .OUTPUTS - Returns a string array with at least one item. -#> -function Get-ClassName -{ - [CmdletBinding()] - [OutputType([System.Object[]])] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [PSObject] - $InputObject, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $Recurse - ) - - # Create a list of the inherited class names - $class = @($InputObject.GetType().FullName) - - if ($Recurse.IsPresent) - { - $parentClass = $InputObject.GetType().BaseType - - while ($parentClass -ne [System.Object]) - { - $class += $parentClass.FullName - - $parentClass = $parentClass.BaseType - } - } - - return ,$class -} -#EndRegion '.\Private\Get-ClassName.ps1' 44 -#Region '.\Private\Get-DesiredStateProperty.ps1' 0 - -<# - .SYNOPSIS - Returns the properties that should be enforced for the desired state. - - .DESCRIPTION - Returns the properties that should be enforced for the desired state. - This function converts a PSObject into a hashtable containing the properties - that should be enforced. - - .PARAMETER InputObject - The object that contain the properties with the desired state. - - .OUTPUTS - Hashtable -#> -function Get-DesiredStateProperty -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [PSObject] - $InputObject - ) - - $desiredStateProperty = $InputObject | ConvertFrom-DscResourceInstance - - <# - Remove properties that have $null as the value, and remove read - properties so that there is no chance to compare those. - #> - @($desiredStateProperty.Keys) | ForEach-Object -Process { - $isReadProperty = $InputObject.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'NotConfigurable' }).NamedArguments.TypedValue.Value -eq $true - - if ($isReadProperty -or $null -eq $desiredStateProperty[$_]) - { - $desiredStateProperty.Remove($_) - } - } - - return $desiredStateProperty -} -#EndRegion '.\Private\Get-DesiredStateProperty.ps1' 45 -#Region '.\Private\Get-KeyProperty.ps1' 0 - -<# - .SYNOPSIS - Returns the DSC resource key property and its value. - - .DESCRIPTION - Returns the DSC resource key property and its value. - - .PARAMETER InputObject - The object that contain the key property. - - .OUTPUTS - Hashtable -#> -function Get-KeyProperty -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [PSObject] - $InputObject - ) - - # Get all key properties. - $keyProperty = $InputObject | - Get-Member -MemberType 'Property' | - Select-Object -ExpandProperty 'Name' | - Where-Object -FilterScript { - $InputObject.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'Key' }).NamedArguments.TypedValue.Value -eq $true - } - - # Return a hashtable containing each key property and its value. - $getKeyPropertyResult = @{} - - $keyProperty | ForEach-Object -Process { - $getKeyPropertyResult.$_ = $InputObject.$_ - } - - return $getKeyPropertyResult -} -#EndRegion '.\Private\Get-KeyProperty.ps1' 43 -#Region '.\Private\Get-LocalizedDataRecursive.ps1' 0 -<# - .SYNOPSIS - Get the localization strings data from one or more localization string files. - This can be used in classes to be able to inherit localization strings - from one or more parent (base) classes. - - The order of class names passed to parameter `ClassName` determines the order - of importing localization string files. First entry's localization string file - will be imported first, then next entry's localization string file, and so on. - If the second (or any consecutive) entry's localization string file contain a - localization string key that existed in a previous imported localization string - file that localization string key will be ignored. Making it possible for a - child class to override localization strings from one or more parent (base) - classes. - - .PARAMETER ClassName - An array of class names, normally provided by `Get-ClassName -Recurse`. - - .OUTPUTS - Returns a string array with at least one item. -#> -function Get-LocalizedDataRecursive -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [System.String[]] - $ClassName - ) - - begin - { - $localizedData = @{} - } - - process - { - foreach ($name in $ClassName) - { - if ($name -match '\.psd1') - { - # Assume we got full file name. - $localizationFileName = $name - } - else - { - # Assume we only got class name. - $localizationFileName = '{0}.strings.psd1' -f $name - } - - Write-Debug -Message ('Importing localization data from {0}' -f $localizationFileName) - - # Get localized data for the class - $classLocalizationStrings = Get-LocalizedData -DefaultUICulture 'en-US' -FileName $localizationFileName -ErrorAction 'Stop' - - # Append only previously unspecified keys in the localization data - foreach ($key in $classLocalizationStrings.Keys) - { - if (-not $localizedData.ContainsKey($key)) - { - $localizedData[$key] = $classLocalizationStrings[$key] - } - } - } - } - - end - { - Write-Debug -Message ('Localization data: {0}' -f ($localizedData | ConvertTo-JSON)) - - return $localizedData - } -} -#EndRegion '.\Private\Get-LocalizedDataRecursive.ps1' 76 -#Region '.\Public\Connect-SqlDscDatabaseEngine.ps1' 0 -<# - .SYNOPSIS - Connect to a SQL Server Database Engine and return the server object. - - .PARAMETER ServerName - String containing the host name of the SQL Server to connect to. - Default value is the current computer name. - - .PARAMETER InstanceName - String containing the SQL Server Database Engine instance to connect to. - Default value is 'MSSQLSERVER'. - - .PARAMETER Credential - The credentials to use to impersonate a user when connecting to the - SQL Server Database Engine instance. If this parameter is left out, then - the current user will be used to connect to the SQL Server Database Engine - instance using Windows Integrated authentication. - - .PARAMETER LoginType - Specifies which type of logon credential should be used. The valid types - are 'WindowsUser' or 'SqlLogin'. Default value is 'WindowsUser' - If set to 'WindowsUser' then the it will impersonate using the Windows - login specified in the parameter Credential. - If set to 'WindowsUser' then the it will impersonate using the native SQL - login specified in the parameter Credential. - - .PARAMETER StatementTimeout - Set the query StatementTimeout in seconds. Default 600 seconds (10 minutes). - - .EXAMPLE - Connect-SqlDscDatabaseEngine - - Connects to the default instance on the local server. - - .EXAMPLE - Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' - - Connects to the instance 'MyInstance' on the local server. - - .EXAMPLE - Connect-SqlDscDatabaseEngine -ServerName 'sql.company.local' -InstanceName 'MyInstance' - - Connects to the instance 'MyInstance' on the server 'sql.company.local'. -#> -function Connect-SqlDscDatabaseEngine -{ - [CmdletBinding(DefaultParameterSetName = 'SqlServer')] - param - ( - [Parameter(ParameterSetName = 'SqlServer')] - [Parameter(ParameterSetName = 'SqlServerWithCredential')] - [ValidateNotNull()] - [System.String] - $ServerName = (Get-ComputerName), - - [Parameter(ParameterSetName = 'SqlServer')] - [Parameter(ParameterSetName = 'SqlServerWithCredential')] - [ValidateNotNull()] - [System.String] - $InstanceName = 'MSSQLSERVER', - - [Parameter(ParameterSetName = 'SqlServerWithCredential', Mandatory = $true)] - [ValidateNotNull()] - [Alias('SetupCredential', 'DatabaseCredential')] - [System.Management.Automation.PSCredential] - $Credential, - - [Parameter(ParameterSetName = 'SqlServerWithCredential')] - [ValidateSet('WindowsUser', 'SqlLogin')] - [System.String] - $LoginType = 'WindowsUser', - - [Parameter()] - [ValidateNotNull()] - [System.Int32] - $StatementTimeout = 600 - ) - - # Call the private function. - return (Connect-Sql @PSBoundParameters) -} -#EndRegion '.\Public\Connect-SqlDscDatabaseEngine.ps1' 82 -#Region '.\Public\Get-SqlDscDatabasePermission.ps1' 0 -<# - .SYNOPSIS - Returns the current permissions for the database principal. - - .PARAMETER ServerObject - Specifies current server connection object. - - .PARAMETER DatabaseName - Specifies the database name. - - .PARAMETER Name - Specifies the name of the database principal for which the permissions are - returned. - - .PARAMETER IgnoreMissingPrincipal - Specifies that the command ignores if the database principal do not exist - which also include if database is not present. - If not passed the command throws an error if the database or database - principal is missing. - - .OUTPUTS - [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] - - .NOTES - This command excludes fixed roles like db_datareader, and will always return - $null for such roles. -#> -function Get-SqlDscDatabasePermission -{ - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because Script Analyzer does not understand type even if cast when using comma in return statement')] - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] - [CmdletBinding()] - [OutputType([Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]])] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [Microsoft.SqlServer.Management.Smo.Server] - $ServerObject, - - [Parameter(Mandatory = $true)] - [System.String] - $DatabaseName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $IgnoreMissingPrincipal - ) - - # Initialize variable permission - $getSqlDscDatabasePermissionResult = $null - - $sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName] - - if ($sqlDatabaseObject) - { - $testSqlDscIsDatabasePrincipalParameters = @{ - ServerObject = $ServerObject - DatabaseName = $DatabaseName - Name = $Name - ExcludeFixedRoles = $true - } - - $isDatabasePrincipal = Test-SqlDscIsDatabasePrincipal @testSqlDscIsDatabasePrincipalParameters - - if ($isDatabasePrincipal) - { - $getSqlDscDatabasePermissionResult = $sqlDatabaseObject.EnumDatabasePermissions($Name) - } - else - { - $missingPrincipalMessage = $script:localizedData.DatabasePermissionMissingPrincipal -f $Name, $DatabaseName - - if ($IgnoreMissingPrincipal.IsPresent) - { - Write-Verbose -Message $missingPrincipalMessage - } - else - { - throw $missingPrincipalMessage - } - } - } - else - { - $missingPrincipalMessage = $script:localizedData.DatabasePermissionMissingDatabase -f $DatabaseName - - if ($IgnoreMissingPrincipal.IsPresent) - { - Write-Verbose -Message $missingPrincipalMessage - } - else - { - throw $missingPrincipalMessage - } - } - - return , $getSqlDscDatabasePermissionResult -} -#EndRegion '.\Public\Get-SqlDscDatabasePermission.ps1' 103 -#Region '.\Public\Test-SqlDscIsDatabasePrincipal.ps1' 0 -<# - .SYNOPSIS - Returns whether the database principal exist. - - .PARAMETER ServerObject - Specifies current server connection object. - - .PARAMETER DatabaseName - Specifies the SQL database name. - - .PARAMETER Name - Specifies the name of the database principal. -#> -function Test-SqlDscIsDatabasePrincipal -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [Microsoft.SqlServer.Management.Smo.Server] - $ServerObject, - - [Parameter(Mandatory = $true)] - [System.String] - $DatabaseName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $ExcludeUsers, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $ExcludeRoles, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $ExcludeFixedRoles, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $ExcludeApplicationRoles - ) - - $principalExist = $false - - $sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName] - - if (-not $ExcludeUsers.IsPresent -and $sqlDatabaseObject.Users[$Name]) - { - $principalExist = $true - } - - if (-not $ExcludeRoles.IsPresent) - { - $userDefinedRole = if ($ExcludeFixedRoles.IsPresent) - { - # Skip fixed roles like db_datareader. - $sqlDatabaseObject.Roles | Where-Object -FilterScript { - -not $_.IsFixedRole -and $_.Name -eq $Name - } - } - else - { - $sqlDatabaseObject.Roles[$Name] - } - - if ($userDefinedRole) - { - $principalExist = $true - } - } - - if (-not $ExcludeApplicationRoles.IsPresent -and $sqlDatabaseObject.ApplicationRoles[$Name]) - { - $principalExist = $true - } - - return $principalExist -} -#EndRegion '.\Public\Test-SqlDscIsDatabasePrincipal.ps1' 85 diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index 92157db58..2f0be4a91 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -11,9 +11,12 @@ class ResourceBase { - # Hidden property for holding localization strings + # Property for holding localization strings hidden [System.Collections.Hashtable] $localizedData = @{} + # Property for derived class to set properties that should not be enforced. + hidden [System.String[]] $notEnforcedProperties = @() + # Default constructor ResourceBase() { @@ -44,8 +47,6 @@ class ResourceBase $getCurrentStateResult = $this.GetCurrentState($keyProperty) - Write-Verbose -Verbose -Message ($getCurrentStateResult | Out-String) - $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) foreach ($propertyName in $this.PSObject.Properties.Name) @@ -56,6 +57,41 @@ class ResourceBase } } + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + $propertiesNotInDesiredState = $this.Compare($getCurrentStateResult) + + <# + Return the correct value for Ensure property if it hasn't been already + set by GetCurrentState(). + #> + if (($this | Test-ResourceHasEnsureProperty) -and -not $getCurrentStateResult.Ensure) + { + if ($propertiesNotInDesiredState) + { + $dscResourceObject.Ensure = [Ensure]::Absent + } + else + { + $dscResourceObject.Ensure = [Ensure]::Present + } + } + + if ($propertiesNotInDesiredState) + { + foreach ($property in $propertiesNotInDesiredState) + { + $dscResourceObject.Reasons += [Reason] @{ + Code = '{0}:{0}:{1}' -f $this.GetType(), $property.Property + Phrase = 'The property {0} should be {1}, but was {2}' -f $property.Property, ($property.ExpectedValue | ConvertTo-Json -Compress), ($property.ActualValue | ConvertTo-Json -Compress) + } + + Write-Verbose -Verbose -Message ($this.Reasons | Out-String) + } + } + # Return properties. return $dscResourceObject } @@ -69,7 +105,10 @@ class ResourceBase $this.Assert() - # Call the Compare method to get enforced properties that are not in desired state. + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> $propertiesNotInDesiredState = $this.Compare() if ($propertiesNotInDesiredState) @@ -128,20 +167,27 @@ class ResourceBase <# Returns a hashtable containing all properties that should be enforced and - are not in desired state. + are not in desired state, or $null if all enforced properties are in + desired state. This method should normally not be overridden. #> hidden [System.Collections.Hashtable[]] Compare() { $currentState = $this.Get() | ConvertFrom-DscResourceInstance + + return $this.Compare($currentState) + } + + hidden [System.Collections.Hashtable[]] Compare([System.Collections.Hashtable] $currentState) + { $desiredState = $this | Get-DesiredStateProperty $CompareDscParameterState = @{ CurrentValues = $currentState DesiredValues = $desiredState Properties = $desiredState.Keys - ExcludeProperties = @('DnsServer') + ExcludeProperties = $this.notEnforcedProperties IncludeValue = $true } @@ -153,6 +199,10 @@ class ResourceBase } # Returns a hashtable containing all properties that should be enforced. + <# + TODO: This should be a private function, e.g ConvertFrom-CompareHashtable, + that could have a [Switch] property 'NameAndExpectedValue' + #> hidden [System.Collections.Hashtable] GetDesiredStateForSplatting([System.Collections.Hashtable[]] $Properties) { $desiredState = @{} diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index ef369141f..5c1354eb1 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -64,11 +64,43 @@ This is an array of CIM instances of class `DatabasePermission` from the namespace `root/Microsoft/Windows/DesiredStateConfiguration`. + .PARAMETER Credential + Specifies the credential to use to connect to the _SQL Server_ instance. + The username of the credentials must be in the format `user@domain`, e.g. + `MySqlUser@company.local`. + + If parameter **Credential'* is not provided then the resource instance is + run using the credential that runs the configuration. + .PARAMETER Ensure If the permission should be granted ('Present') or revoked ('Absent'). .PARAMETER Reasons Returns the reason a property is not in desired state. + + .EXAMPLE + Invoke-DscResource -ModuleName SqlServerDsc -Name SqlDatabasePermission -Method Get -Property @{ + Ensure = 'Present' + ServerName = 'localhost' + InstanceName = 'SQL2017' + DatabaseName = 'AdventureWorks' + Credential = $SqlInstallCredential + Name = 'SQLTEST\sqluser' + Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'Grant' + Permission = @('select') + } + ) + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'GrantWithGrant' + Permission = @('update') + } + ) + ) + } #> # TODO: Add this if PsDscRunAsCredential is not supported. @@ -108,6 +140,18 @@ class SqlDatabasePermission : ResourceBase [Reason[]] $Reasons + SqlDatabasePermission() : base () + { + # These properties will not be enforced. + $this.notEnforcedProperties = @( + 'ServerName' + 'InstanceName' + 'DatabaseName' + 'Name' + 'Credential' + ) + } + [SqlDatabasePermission] Get() { # Call the base method to return the properties. @@ -132,13 +176,9 @@ class SqlDatabasePermission : ResourceBase #> hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { + # The property Ensure will be handled by the base class. $currentState = @{ - Ensure = 'Absent' - ServerName = $this.ServerName - InstanceName = $properties.InstanceName - DatabaseName = $properties.DatabaseName Permission = [DatabasePermission[]] @() - Name = $properties.Name } $connectSqlDscDatabaseEngineParameters = @{ @@ -153,7 +193,7 @@ class SqlDatabasePermission : ResourceBase $sqlServerObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters - # TA BORT -VERBOSE! + # TODO: TA BORT -VERBOSE! Write-Verbose -Verbose -Message ( $this.localizedData.EvaluateDatabasePermissionForPrincipal -f @( $properties.Name, diff --git a/source/Private/Test-ResourceHasEnsureProperty.ps1 b/source/Private/Test-ResourceHasEnsureProperty.ps1 new file mode 100644 index 000000000..a897b2f6c --- /dev/null +++ b/source/Private/Test-ResourceHasEnsureProperty.ps1 @@ -0,0 +1,38 @@ + +<# + .SYNOPSIS + Tests wether the class-based resource has an Ensure property. + + .DESCRIPTION + Tests wether the class-based resource has an Ensure property. + + .PARAMETER InputObject + The object that should be tested for existens of property Ensure. + + .OUTPUTS + [Boolean] +#> +function Test-ResourceHasEnsureProperty +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject + ) + + $hasEnsure = $false + + # Get all key properties. + $ensureProperty = $InputObject | + Get-Member -MemberType 'Property' -Name 'Ensure' + + if ($ensureProperty) + { + $hasEnsure = $true + } + + return $hasEnsure +} From 4d76980992388a1b6a354af22a2d840e5cfb5f95 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 4 Jul 2022 21:30:47 +0200 Subject: [PATCH 23/98] Fix unit tests --- source/Classes/020.SimpleResource.ps1 | 75 ---- source/Classes/020.SqlDatabasePermission.ps1 | 2 +- .../1-GrantDatabasePermissions.ps1 | 66 ++-- .../2-RevokeDatabasePermissions.ps1 | 24 +- .../3-DenyDatabasePermissions.ps1 | 36 +- source/Private/Get-KeyProperty.ps1 | 7 +- .../Test-ResourceHasEnsureProperty.ps1 | 10 +- .../Public/Get-SqlDscDatabasePermission.ps1 | 37 +- .../Public/Test-SqlDscIsDatabasePrincipal.ps1 | 28 +- source/en-US/SimpleResource.strings.psd1 | 8 - source/en-US/SqlServerDsc.strings.psd1 | 7 +- .../Test-ResourceHasEnsureProperty.Tests.ps1 | 133 +++++++ .../Test-SqlDscIsDatabasePrincipal.Tests.ps1 | 342 ++++++++++++++++++ 13 files changed, 589 insertions(+), 186 deletions(-) delete mode 100644 source/Classes/020.SimpleResource.ps1 delete mode 100644 source/en-US/SimpleResource.strings.psd1 create mode 100644 tests/Unit/Private/Test-ResourceHasEnsureProperty.Tests.ps1 create mode 100644 tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 diff --git a/source/Classes/020.SimpleResource.ps1 b/source/Classes/020.SimpleResource.ps1 deleted file mode 100644 index 1c340b920..000000000 --- a/source/Classes/020.SimpleResource.ps1 +++ /dev/null @@ -1,75 +0,0 @@ -<# - .SYNOPSIS - Resource for testing. -#> - -[DscResource()] -class SimpleResource : ResourceBase -{ - [DscProperty(Key)] - [System.String] - $InstanceName - - [DscProperty(Key)] - [System.String] - $DatabaseName - - [DscProperty(Key)] - [System.String] - $Name - - [DscProperty()] - [System.String] - $ServerName = (Get-ComputerName) - - [DscProperty(Mandatory)] - [DatabasePermission[]] - $Permission - - [DscProperty()] - [Ensure] - $Ensure = [Ensure]::Present - - [DscProperty(NotConfigurable)] - [Reason[]] - $Reasons - - [SimpleResource] Get() - { - $dscResourceObject = [SimpleResource] @{ - InstanceName = 'SQL2017' - DatabaseName = 'MyDB' - Name = 'MyPrincipal' - ServerName = 'MyHost' - Ensure = 'Present' - Permission = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('CONNECT') - } - [DatabasePermission] @{ - State = 'Deny' - Permission = @('SELECT') - } - ) - Reasons = [Reason[]] @( - [Reason] @{ - Code = '{0}:{0}:Ensure' -f $this.GetType() - Phrase = 'The property Ensure should be Present, but was Absent' - } - ) - } - - return $dscResourceObject - } - - [System.Boolean] Test() - { - return $true - } - - [void] Set() - { - Write-Verbose -Verbose -Message 'NotImplemented: Set()' - } -} diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 5c1354eb1..91d37d655 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -203,7 +203,7 @@ class SqlDatabasePermission : ResourceBase ) $databasePermissionInfo = $sqlServerObject | - Get-SqlDscDatabasePermission -DatabaseName $this.DatabaseName -Name $this.Name -IgnoreMissingPrincipal + Get-SqlDscDatabasePermission -DatabaseName $this.DatabaseName -Name $this.Name -ErrorAction 'SilentlyContinue' if ($databasePermissionInfo) { diff --git a/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 index 424b9a914..2e730255c 100644 --- a/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 +++ b/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 @@ -22,53 +22,47 @@ Configuration Example Ensure = 'Present' Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorks' - Permission = [CimInstance[]] @( - ( - New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ - State = 'Grant' - Permission = @('Connect', 'Update') - } - ) + Permission = @( + DatabasePermission { + State = 'Grant' + Permission = @('Connect', 'Update') + } ) - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Credential = $SqlAdministratorCredential + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Credential = $SqlAdministratorCredential } SqlDatabasePermission 'Grant_SqlDatabasePermissions_SQLUser_Db01' { - Ensure = 'Present' - Name = 'CONTOSO\SQLUser' - DatabaseName = 'AdventureWorks' - Permission = [CimInstance[]] @( - ( - New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ - State = 'Grant' - Permission = @('Connect', 'Update') - } - ) + Ensure = 'Present' + Name = 'CONTOSO\SQLUser' + DatabaseName = 'AdventureWorks' + Permission = @( + DatabasePermission { + State = 'Grant' + Permission = @('Connect', 'Update') + } ) - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Credential = $SqlAdministratorCredential + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Credential = $SqlAdministratorCredential } SqlDatabasePermission 'Grant_SqlDatabasePermissions_SQLAdmin_Db02' { - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorksLT' - Permission = [CimInstance[]] @( - ( - New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ - State = 'Grant' - Permission = @('Connect', 'Update') - } - ) + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + DatabaseName = 'AdventureWorksLT' + Permission = @( + DatabasePermission { + State = 'Grant' + Permission = @('Connect', 'Update') + } ) - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Credential = $SqlAdministratorCredential + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Credential = $SqlAdministratorCredential } } } diff --git a/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 index d174b220c..781f64f1c 100644 --- a/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 +++ b/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 @@ -22,13 +22,11 @@ Configuration Example Ensure = 'Absent' Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorks' - Permission = [CimInstance[]] @( - ( - New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ - State = 'Grant' - Permission = @('Connect', 'Update') - } - ) + Permission = @( + DatabasePermission { + State = 'Grant' + Permission = @('Connect', 'Update') + } ) ServerName = 'sqltest.company.local' InstanceName = 'DSC' @@ -40,13 +38,11 @@ Configuration Example Ensure = 'Absent' Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorks' - Permission = [CimInstance[]] @( - ( - New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ - State = 'Deny' - Permission = @('Select', 'CreateTable') - } - ) + Permission = @( + DatabasePermission { + State = 'Grant' + Permission = @('Connect', 'Update') + } ) ServerName = 'sqltest.company.local' InstanceName = 'DSC' diff --git a/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 index 390332480..7a65d66c4 100644 --- a/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 +++ b/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 @@ -22,13 +22,11 @@ Configuration Example Ensure = 'Present' Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorks' - Permission = [CimInstance[]] @( - ( - New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ - State = 'Deny' - Permission = @('Select', 'CreateTable') - } - ) + Permission = @( + DatabasePermission { + State = 'Deny' + Permission = @('Select', 'CreateTable') + } ) ServerName = 'sqltest.company.local' InstanceName = 'DSC' @@ -41,13 +39,11 @@ Configuration Example Ensure = 'Present' Name = 'CONTOSO\SQLUser' DatabaseName = 'AdventureWorks' - Permission = [CimInstance[]] @( - ( - New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ - State = 'Deny' - Permission = @('Select', 'CreateTable') - } - ) + Permission = @( + DatabasePermission { + State = 'Deny' + Permission = @('Select', 'CreateTable') + } ) ServerName = 'sqltest.company.local' InstanceName = 'DSC' @@ -60,13 +56,11 @@ Configuration Example Ensure = 'Present' Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorksLT' - Permission = [CimInstance[]] @( - ( - New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ - State = 'Deny' - Permission = @('Select', 'CreateTable') - } - ) + Permission = @( + DatabasePermission { + State = 'Deny' + Permission = @('Select', 'CreateTable') + } ) ServerName = 'sqltest.company.local' InstanceName = 'DSC' diff --git a/source/Private/Get-KeyProperty.ps1 b/source/Private/Get-KeyProperty.ps1 index 69d50f7ff..d40abd897 100644 --- a/source/Private/Get-KeyProperty.ps1 +++ b/source/Private/Get-KeyProperty.ps1 @@ -28,7 +28,12 @@ function Get-KeyProperty Get-Member -MemberType 'Property' | Select-Object -ExpandProperty 'Name' | Where-Object -FilterScript { - $InputObject.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'Key' }).NamedArguments.TypedValue.Value -eq $true + $InputObject.GetType().GetMember($_).CustomAttributes.Where( + { + $_.AttributeType.Name -eq 'DscPropertyAttribute' -and + $_.NamedArguments.MemberName -eq 'Key' + } + ).NamedArguments.TypedValue.Value -eq $true } # Return a hashtable containing each key property and its value. diff --git a/source/Private/Test-ResourceHasEnsureProperty.ps1 b/source/Private/Test-ResourceHasEnsureProperty.ps1 index a897b2f6c..2a81dbd9c 100644 --- a/source/Private/Test-ResourceHasEnsureProperty.ps1 +++ b/source/Private/Test-ResourceHasEnsureProperty.ps1 @@ -1,4 +1,3 @@ - <# .SYNOPSIS Tests wether the class-based resource has an Ensure property. @@ -27,7 +26,14 @@ function Test-ResourceHasEnsureProperty # Get all key properties. $ensureProperty = $InputObject | - Get-Member -MemberType 'Property' -Name 'Ensure' + Get-Member -MemberType 'Property' -Name 'Ensure' | + Where-Object -FilterScript { + $InputObject.GetType().GetMember($_.Name).CustomAttributes.Where( + { + $_.AttributeType.Name -eq 'DscPropertyAttribute' + } + ) + } if ($ensureProperty) { diff --git a/source/Public/Get-SqlDscDatabasePermission.ps1 b/source/Public/Get-SqlDscDatabasePermission.ps1 index 34330edb4..ab0a4c92e 100644 --- a/source/Public/Get-SqlDscDatabasePermission.ps1 +++ b/source/Public/Get-SqlDscDatabasePermission.ps1 @@ -43,17 +43,18 @@ function Get-SqlDscDatabasePermission [Parameter(Mandatory = $true)] [System.String] - $Name, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $IgnoreMissingPrincipal + $Name ) # Initialize variable permission $getSqlDscDatabasePermissionResult = $null - $sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName] + $sqlDatabaseObject = $null + + if ($sqlServerObject.Databases) + { + $sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName] + } if ($sqlDatabaseObject) { @@ -72,30 +73,16 @@ function Get-SqlDscDatabasePermission } else { - $missingPrincipalMessage = $script:localizedData.DatabasePermissionMissingPrincipal -f $Name, $DatabaseName - - if ($IgnoreMissingPrincipal.IsPresent) - { - Write-Verbose -Message $missingPrincipalMessage - } - else - { - throw $missingPrincipalMessage - } + $missingPrincipalMessage = $script:localizedData.DatabasePermission_MissingPrincipal -f $Name, $DatabaseName + + Write-Error -Message $missingPrincipalMessage -Category 'InvalidOperation' -ErrorId 'GSDDP0001' -TargetObject $Name } } else { - $missingPrincipalMessage = $script:localizedData.DatabasePermissionMissingDatabase -f $DatabaseName + $missingDatabaseMessage = $script:localizedData.DatabasePermission_MissingDatabase -f $DatabaseName - if ($IgnoreMissingPrincipal.IsPresent) - { - Write-Verbose -Message $missingPrincipalMessage - } - else - { - throw $missingPrincipalMessage - } + Write-Error -Message $missingDatabaseMessage -Category 'InvalidOperation' -ErrorId 'GSDDP0002' -TargetObject $DatabaseName } return , $getSqlDscDatabasePermissionResult diff --git a/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 b/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 index 9b28a354a..e0139e589 100644 --- a/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 +++ b/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 @@ -10,6 +10,20 @@ .PARAMETER Name Specifies the name of the database principal. + + .PARAMETER ExcludeUsers + Specifies that database users should not be evaluated. + + .PARAMETER ExcludeRoles + Specifies that database roles should not be evaluated for the specified + name. This will also exclude fixed roles. + + .PARAMETER ExcludeFixedRoles + Specifies that fixed roles should not be evaluated for the specified name. + + .PARAMETER ExcludeApplicationRoles + Specifies that fixed application roles should not be evaluated for the + specified name. #> function Test-SqlDscIsDatabasePrincipal { @@ -48,7 +62,19 @@ function Test-SqlDscIsDatabasePrincipal $principalExist = $false - $sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName] + $sqlDatabaseObject = $ServerObject.Databases[$DatabaseName] + + if (-not $sqlDatabaseObject) + { + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + ($script:localizedData.IsDatabasePrincipal_DatabaseMissing -f $DatabaseName), + 'TSDISO0001', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $DatabaseName + ) + ) + } if (-not $ExcludeUsers.IsPresent -and $sqlDatabaseObject.Users[$Name]) { diff --git a/source/en-US/SimpleResource.strings.psd1 b/source/en-US/SimpleResource.strings.psd1 deleted file mode 100644 index 13d2848dd..000000000 --- a/source/en-US/SimpleResource.strings.psd1 +++ /dev/null @@ -1,8 +0,0 @@ -<# - .SYNOPSIS - The localized resource strings in English (en-US) for the - resource SimpleResource. -#> - -ConvertFrom-StringData @' -'@ diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 3dfef20a8..15e736d48 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -7,6 +7,9 @@ ConvertFrom-StringData @' # Get-SqlDscDatabasePermission - DatabasePermissionMissingPrincipal = The database principal '{0}' is neither a user, database role (user-defined), or database application role in the database '{1}'. (GETSDP0001). - DatabasePermissionMissingDatabase = The database '{0}' did not exist. (GETSDP0002) + DatabasePermission_MissingPrincipal = The database principal '{0}' is neither a user, database role (user-defined), or database application role in the database '{1}'. + DatabasePermission_MissingDatabase = The database '{0}' cannot be found. + + # Test-SqlDscIsDatabasePrincipal + IsDatabasePrincipal_DatabaseMissing = The database '{0}' cannot be found. '@ diff --git a/tests/Unit/Private/Test-ResourceHasEnsureProperty.Tests.ps1 b/tests/Unit/Private/Test-ResourceHasEnsureProperty.Tests.ps1 new file mode 100644 index 000000000..4d296fc17 --- /dev/null +++ b/tests/Unit/Private/Test-ResourceHasEnsureProperty.Tests.ps1 @@ -0,0 +1,133 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Test-ResourceHasEnsureProperty' -Tag 'Private' { + Context 'When resource does not have an Ensure property' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it outside the here-string PowerShell + will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty()] + [System.String] + $MyResourceProperty3 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceHasEnsureProperty -InputObject $script:mockResourceBaseInstance + + $result | Should -BeFalse + } + } + } + + Context 'When resource have an Ensure property' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it outside the here-string PowerShell + will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty()] + [System.String] + $Ensure + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceHasEnsureProperty -InputObject $script:mockResourceBaseInstance + + $result | Should -BeTrue + } + } + } +} diff --git a/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 b/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 new file mode 100644 index 000000000..0c5a969c8 --- /dev/null +++ b/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 @@ -0,0 +1,342 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { + Context 'When database does not have the specified principal' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @( + @{ + 'AdventureWorks' = New-Object -TypeName Object + } + ) + } -PassThru -Force + } + + It 'Should throw the correct error' { + { Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'MissingDatabase' -Name 'KnownUser' } | + Should -Throw ('The database ''MissingDatabase'' cannot be found.') + } + } + + Context 'When database does not have the specified principal' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UnknownUser' + + $result | Should -BeFalse + } + } + + Context 'When database have the specified principal' { + Context 'When the specified principal is a user' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $true' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'Zebes\SamusAran' + + $result | Should -BeTrue + } + + Context 'When users are excluded from evaluation' { + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'Zebes\SamusAran' -ExcludeUsers + + $result | Should -BeFalse + } + } + } + + Context 'When the specified principal is a user defined role' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $true' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UserDefinedRole' + + $result | Should -BeTrue + } + + Context 'When roles are excluded from evaluation' { + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UserDefinedRole' -ExcludeRoles + + $result | Should -BeFalse + } + } + + Context 'When fixed roles are excluded from evaluation' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return ( + @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + ).GetEnumerator() | Select-Object -ExpandProperty Value + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $true' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UserDefinedRole' -ExcludeFixedRoles + + $result | Should -BeTrue + } + } + } + + Context 'When the specified principal is a fixed role' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $true' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'db_datareader' + + $result | Should -BeTrue + } + + Context 'When roles are excluded from evaluation' { + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'db_datareader' -ExcludeRoles + + $result | Should -BeFalse + } + } + + Context 'When fixed roles are excluded from evaluation' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return ( + @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + ).GetEnumerator() | Select-Object -ExpandProperty Value + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'db_datareader' -ExcludeFixedRoles + + $result | Should -BeFalse + } + } + } + } +} From 5e790f627a914de70557ccf3bf70a20d37d9739a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 4 Jul 2022 21:33:50 +0200 Subject: [PATCH 24/98] Fix unit test --- .../Test-SqlDscIsDatabasePrincipal.Tests.ps1 | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 b/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 index 0c5a969c8..9e74c0cad 100644 --- a/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 +++ b/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 @@ -155,6 +155,55 @@ Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { } } + Context 'When the specified principal is a application role' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $true' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'MyAppRole' + + $result | Should -BeTrue + } + + Context 'When application roles are excluded from evaluation' { + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'MyAppRole' -ExcludeApplicationRoles + + $result | Should -BeFalse + } + } + } + Context 'When the specified principal is a user defined role' { BeforeAll { $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | From 97cc7c1a85b53f66ef5e23c5d06fe8b106fff086 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 5 Jul 2022 19:08:07 +0200 Subject: [PATCH 25/98] Fix unit tests --- .../Public/Get-SqlDscDatabasePermission.ps1 | 24 +- .../Connect-SqlDscDatabaseEngine.Tests.ps1 | 79 +++++++ .../Get-SqlDscDatabasePermission.Tests.ps1 | 219 ++++++++++++++++++ .../Test-SqlDscIsDatabasePrincipal.Tests.ps1 | 154 ++++++------ 4 files changed, 396 insertions(+), 80 deletions(-) create mode 100644 tests/Unit/Public/Connect-SqlDscDatabaseEngine.Tests.ps1 create mode 100644 tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 diff --git a/source/Public/Get-SqlDscDatabasePermission.ps1 b/source/Public/Get-SqlDscDatabasePermission.ps1 index ab0a4c92e..9364f1857 100644 --- a/source/Public/Get-SqlDscDatabasePermission.ps1 +++ b/source/Public/Get-SqlDscDatabasePermission.ps1 @@ -12,18 +12,25 @@ Specifies the name of the database principal for which the permissions are returned. - .PARAMETER IgnoreMissingPrincipal - Specifies that the command ignores if the database principal do not exist - which also include if database is not present. - If not passed the command throws an error if the database or database - principal is missing. - .OUTPUTS [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Get-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' + + Connects to the default instance on the current machine and returns the + database permission information for the specified principal. + .NOTES - This command excludes fixed roles like db_datareader, and will always return - $null for such roles. + This command excludes fixed roles like _db_datareader_ by default, and will + always return `$null` if a fixed role is specified as **Name**. + + If specifying `-ErrorAction 'SilentlyContinue'` then the command will silently + ignore if the database (parameter **DatabaseName**) is not present or the + database principal is not present. In such case the command will return `$false`. + If specifying `-ErrorAction 'Stop'` the command will throw an error if the + database or database principal is missing. #> function Get-SqlDscDatabasePermission { @@ -46,7 +53,6 @@ function Get-SqlDscDatabasePermission $Name ) - # Initialize variable permission $getSqlDscDatabasePermissionResult = $null $sqlDatabaseObject = $null diff --git a/tests/Unit/Public/Connect-SqlDscDatabaseEngine.Tests.ps1 b/tests/Unit/Public/Connect-SqlDscDatabaseEngine.Tests.ps1 new file mode 100644 index 000000000..ea2360776 --- /dev/null +++ b/tests/Unit/Public/Connect-SqlDscDatabaseEngine.Tests.ps1 @@ -0,0 +1,79 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'because ConvertTo-SecureString is used to simplify the tests.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Connect-SqlDscDatabaseEngine' -Tag 'Public' { + BeforeAll { + Mock -CommandName Connect-Sql + + $mockCredentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @( + 'COMPANY\SqlAdmin', + ('dummyPassW0rd' | ConvertTo-SecureString -AsPlainText -Force) + ) + + } + + It 'Should call the correct mock with the expected parameters' { + $mockConnectSqlDscDatabaseEngineParameters = @{ + ServerName = 'MyServer' + InstanceName = 'MyInstance' + Credential = $mockCredentials + LoginType = 'WindowsUser' + StatementTimeout = 800 + } + + Connect-SqlDscDatabaseEngine @mockConnectSqlDscDatabaseEngineParameters + + Should -Invoke -CommandName Connect-Sql -ParameterFilter { + $ServerName -eq 'MyServer' -and + $InstanceName -eq 'MyInstance' -and + $Credential -eq $mockCredentials -and + $LoginType -eq 'WindowsUser' -and + $StatementTimeout -eq 800 + } + } +} diff --git a/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 b/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 new file mode 100644 index 000000000..2394c3231 --- /dev/null +++ b/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 @@ -0,0 +1,219 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-SqlDscDatabasePermission' -Tag 'Public' { + Context 'When no databases exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return $null + } -PassThru -Force + } + + Context 'When specifying to throw on error' { + BeforeAll { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingDatabase + } + } + + It 'Should throw the correct error' { + { Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'MissingDatabase' -Name 'KnownUser' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') + } + } + + Context 'When ignoring the error' { + It 'Should not throw an exception and return $null' { + Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'MissingDatabase' -Name 'KnownUser' -ErrorAction 'SilentlyContinue' | + Should -BeNullOrEmpty + } + } + } + + Context 'When the database does not exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'AdventureWorks' -PassThru -Force + } + } -PassThru -Force + } + + Context 'When specifying to throw on error' { + BeforeAll { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingDatabase + } + } + + It 'Should throw the correct error' { + { Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'MissingDatabase' -Name 'KnownUser' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') + } + } + + Context 'When ignoring the error' { + It 'Should not throw an exception and return $null' { + Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'MissingDatabase' -Name 'KnownUser' -ErrorAction 'SilentlyContinue' | + Should -BeNullOrEmpty + } + } + } + + Context 'When the database principal does not exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'AdventureWorks' -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $false + } + } + + Context 'When specifying to throw on error' { + BeforeAll { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingPrincipal + } + } + + It 'Should throw the correct error' { + { Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UnknownUser' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'UnknownUser', 'AdventureWorks') + } + } + + Context 'When ignoring the error' { + It 'Should not throw an exception and return $null' { + Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UnknownUser' -ErrorAction 'SilentlyContinue' | + Should -BeNullOrEmpty + } + } + } + + Context 'When the database principal exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'EnumDatabasePermissions' -Value { + param + ( + [Parameter()] + [System.String] + $SqlServerLogin + ) + + $mockEnumDatabasePermissions = @() + + $mockEnumDatabasePermissions += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'Zebes\SamusAran' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockEnumDatabasePermissions += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'Zebes\SamusAran' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + return $mockEnumDatabasePermissions + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + } + + It 'Should return the correct values' { + $mockResult = Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'Zebes\SamusAran' -ErrorAction 'Stop' + + $mockResult | Should -HaveCount 2 + + $mockResult[0].PermissionState | Should -Be 'Grant' + $mockResult[0].PermissionType.Connect | Should -BeTrue + $mockResult[0].PermissionType.Update | Should -BeFalse + + $mockResult[1].PermissionState | Should -Be 'Grant' + $mockResult[1].PermissionType.Connect | Should -BeFalse + $mockResult[1].PermissionType.Update | Should -BeTrue + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockServerObject | Get-SqlDscDatabasePermission -DatabaseName 'AdventureWorks' -Name 'Zebes\SamusAran' -ErrorAction 'Stop' + + $mockResult | Should -HaveCount 2 + + $mockResult[0].PermissionState | Should -Be 'Grant' + $mockResult[0].PermissionType.Connect | Should -BeTrue + $mockResult[0].PermissionType.Update | Should -BeFalse + + $mockResult[1].PermissionState | Should -Be 'Grant' + $mockResult[1].PermissionType.Connect | Should -BeFalse + $mockResult[1].PermissionType.Update | Should -BeTrue + } + } + } +} diff --git a/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 b/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 index 9e74c0cad..6a3c0ee14 100644 --- a/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 +++ b/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 @@ -56,59 +56,21 @@ Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { } ) } -PassThru -Force + + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.IsDatabasePrincipal_DatabaseMissing + } } It 'Should throw the correct error' { { Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'MissingDatabase' -Name 'KnownUser' } | - Should -Throw ('The database ''MissingDatabase'' cannot be found.') + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') } } Context 'When database does not have the specified principal' { BeforeAll { $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { - return @{ - 'AdventureWorks' = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { - return @{ - 'Zebes\SamusAran' = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force - } - } -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { - return @{ - 'MyAppRole' = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force - } - } -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { - return @{ - 'db_datareader' = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | - Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force - - 'UserDefinedRole' = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | - Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force - } - } -PassThru -Force - } - } -PassThru -Force - } - - It 'Should return $false' { - $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UnknownUser' - - $result | Should -BeFalse - } - } - - Context 'When database have the specified principal' { - Context 'When the specified principal is a user' { - BeforeAll { - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { return @{ 'AdventureWorks' = New-Object -TypeName Object | @@ -138,6 +100,48 @@ Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { } -PassThru -Force } } -PassThru -Force + } + + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UnknownUser' + + $result | Should -BeFalse + } + } + + Context 'When database have the specified principal' { + Context 'When the specified principal is a user' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force } It 'Should return $true' { @@ -146,6 +150,14 @@ Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { $result | Should -BeTrue } + Context 'When passing ServerObject over the pipeline' { + It 'Should return $true' { + $result = $mockServerObject | Test-SqlDscIsDatabasePrincipal -DatabaseName 'AdventureWorks' -Name 'Zebes\SamusAran' + + $result | Should -BeTrue + } + } + Context 'When users are excluded from evaluation' { It 'Should return $false' { $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'Zebes\SamusAran' -ExcludeUsers @@ -158,35 +170,35 @@ Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { Context 'When the specified principal is a application role' { BeforeAll { $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { - return @{ - 'AdventureWorks' = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { - return @{ - 'Zebes\SamusAran' = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force - } - } -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { - return @{ - 'MyAppRole' = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force - } - } -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { - return @{ - 'db_datareader' = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | - Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force - 'UserDefinedRole' = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | - Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force - } - } -PassThru -Force - } - } -PassThru -Force + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force } It 'Should return $true' { From 8e663e382a4f1973d339e1699144b09bcde9e342 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 5 Jul 2022 21:21:37 +0200 Subject: [PATCH 26/98] Update unit tests --- build.yaml | 3 ++- tests/Unit/Classes/ResourceBase.Tests.ps1 | 4 ++-- tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 | 4 ++-- tests/Unit/Private/Get-KeyProperty.Tests.ps1 | 4 ++-- .../Unit/Private/Test-ResourceHasEnsureProperty.Tests.ps1 | 8 ++++---- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/build.yaml b/build.yaml index d3d4d4be9..3de2af0b4 100644 --- a/build.yaml +++ b/build.yaml @@ -89,7 +89,8 @@ Pester: CoveragePercentTarget: 85 OutputPath: JaCoCo_coverage.xml OutputEncoding: ascii - UseBreakpoints: false + # TODO: There is a bug in Pester when running unit tests for class ResourceBase when 'UseBreakpoints' is turned off. + UseBreakpoints: true TestResult: OutputFormat: NUnitXML OutputEncoding: ascii diff --git a/tests/Unit/Classes/ResourceBase.Tests.ps1 b/tests/Unit/Classes/ResourceBase.Tests.ps1 index 58e0f497e..a1ea41f52 100644 --- a/tests/Unit/Classes/ResourceBase.Tests.ps1 +++ b/tests/Unit/Classes/ResourceBase.Tests.ps1 @@ -102,8 +102,8 @@ Describe 'ResourceBase\Get()' -Tag 'Get' { <# Must use a here-string because we need to pass 'using' which must be - first in a scriptblock, but if it outside the here-string PowerShell - will fail to parse the test script. + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' using module SqlServerDsc diff --git a/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 b/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 index 5b223527f..78349ab32 100644 --- a/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 +++ b/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 @@ -46,8 +46,8 @@ Describe 'Get-DesiredStateProperty' -Tag 'Private' { BeforeAll { <# Must use a here-string because we need to pass 'using' which must be - first in a scriptblock, but if it outside the here-string PowerShell - will fail to parse the test script. + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' using module SqlServerDsc diff --git a/tests/Unit/Private/Get-KeyProperty.Tests.ps1 b/tests/Unit/Private/Get-KeyProperty.Tests.ps1 index f329c895c..16bc625cd 100644 --- a/tests/Unit/Private/Get-KeyProperty.Tests.ps1 +++ b/tests/Unit/Private/Get-KeyProperty.Tests.ps1 @@ -46,8 +46,8 @@ Describe 'Get-KeyProperty' -Tag 'Private' { BeforeAll { <# Must use a here-string because we need to pass 'using' which must be - first in a scriptblock, but if it outside the here-string PowerShell - will fail to parse the test script. + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' using module SqlServerDsc diff --git a/tests/Unit/Private/Test-ResourceHasEnsureProperty.Tests.ps1 b/tests/Unit/Private/Test-ResourceHasEnsureProperty.Tests.ps1 index 4d296fc17..90bd4b970 100644 --- a/tests/Unit/Private/Test-ResourceHasEnsureProperty.Tests.ps1 +++ b/tests/Unit/Private/Test-ResourceHasEnsureProperty.Tests.ps1 @@ -47,8 +47,8 @@ Describe 'Test-ResourceHasEnsureProperty' -Tag 'Private' { BeforeAll { <# Must use a here-string because we need to pass 'using' which must be - first in a scriptblock, but if it outside the here-string PowerShell - will fail to parse the test script. + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' using module SqlServerDsc @@ -91,8 +91,8 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() BeforeAll { <# Must use a here-string because we need to pass 'using' which must be - first in a scriptblock, but if it outside the here-string PowerShell - will fail to parse the test script. + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' using module SqlServerDsc From f1909b556e238b5cb49008c37eb68245568bd47a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 5 Jul 2022 21:22:46 +0200 Subject: [PATCH 27/98] update comment --- build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/build.yaml b/build.yaml index 3de2af0b4..088f94fba 100644 --- a/build.yaml +++ b/build.yaml @@ -90,6 +90,7 @@ Pester: OutputPath: JaCoCo_coverage.xml OutputEncoding: ascii # TODO: There is a bug in Pester when running unit tests for class ResourceBase when 'UseBreakpoints' is turned off. + # See error in gist: https://gist.github.com/johlju/c16dfd9587c7e066e8825fc54b33a703 UseBreakpoints: true TestResult: OutputFormat: NUnitXML From ee21b27eb4d26276d4228ff2ce8d4ad192a44bf5 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 6 Jul 2022 14:21:14 +0200 Subject: [PATCH 28/98] FIx CONTRIBUTING.md --- CONTRIBUTING.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30c7b5d82..4694f569c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -277,3 +277,89 @@ SQL Server 2016, SQL Server 2017 and SQL Server 2019. When sending in a Pull Request (PR) all example files will be tested so they can be compiled to a .mof file. If the tests find any errors the build will fail. + +### Commands + +Commands are publicly exported commands from the module, and the source for +commands are located in the folder `./source/Public`. + +#### Non-Terminating Error + +A non-terminating error should only be used when a command shall be able to +handle (ignoring) an error and continue processing and still give the user +an expected outcome. + +With a non-terminating error the user is able to decide whether the command +should throw or continue processing on error. The user can pass the +parameter and value `-ErrorAction 'SilentlyContinue'` to the command to +ignore the error and allowing the command to continue, for example the +command could then return `$null`. But if the user passes the parameter +and value `-ErrorAction 'Stop'` the same error will throw a terminating +error telling the user the expected outcome could not be achieved. + +The below example checks to see if a database exist, if it doesn't a +non-terminating error are called. The user is able to either ignore the +error or have it throw depending on what value the user specifies +in parameter `ErrorAction` (or `$ErrorActionPreference`). + +```powershell +if (-not $databaseExist) +{ + $errorMessage = $script:localizedData.MissingDatabase -f $DatabaseName + + Write-Error -Message $errorMessage -Category 'InvalidOperation' -ErrorId 'GS0001' -TargetObject $DatabaseName +} +``` + +#### Terminating Error + +A terminating error is an error that the user are not able to ignore by +passing a parameter to the command (like for non-terminating errors). + +If a command shall throw an terminating error the statement `throw` shall +not be used, neither shall the command `Write-Error` be used with the parameter +`-ErrorAction `Stop``. Instead the method `$PSCmdlet.ThrowTerminatingError()` +shall be used to throw a terminating error. + +>**NOTE:** Below output assumes `$ErrorView` is set to `'NormalView'` in the +>PowerShell session. + +When using `throw` it will fail on the line with the throw statement +making it look like it is that statement inside the function that failed, +which is not correct since it is either a previous command or evaluation +that failed resulting in the line with the `throw` being called. This is +an example when using `throw`: + +```plaintext +Exception: +Line | + 2 | throw 'My error' + | ~~~~~~~~~~~~~~~~ + | My error +``` + +When instead using `$PSCmdlet.ThrowTerminatingError()`: + +```powershell +$PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + 'MyError', + 'GS0001', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + 'MyObjectOrValue' + ) +) +``` + +The result from `$PSCmdlet.ThrowTerminatingError()` shows that the command +failed (in this example `Get-Something`) and returns a clear category and +error code. + +```plaintext +Get-Something : My Error +At line:1 char:1 ++ Get-Something ++ ~~~~~~~~~~~~~ ++ CategoryInfo : InvalidOperation: (MyObjectOrValue:String) [Get-Something], Exception ++ FullyQualifiedErrorId : GS0001,Get-Something +``` From b6aeaac036587c65de4d947f68e5962121f60f79 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 6 Jul 2022 21:57:09 +0200 Subject: [PATCH 29/98] Fix unit test --- source/Classes/010.ResourceBase.ps1 | 63 ++- tests/Unit/Classes/ResourceBase.Tests.ps1 | 568 +++++++++++++++++++++- 2 files changed, 607 insertions(+), 24 deletions(-) diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index 2f0be4a91..6cf320fd6 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -57,19 +57,34 @@ class ResourceBase } } + # TODO: If $getCurrentStateResult does not contain Ensure (or null) then + # we must remove Ensure from the comparison by calling a Compare() override + # that takes an array with properties that should be ignored. + $ignoreProperty = @() + + if (($this | Test-ResourceHasEnsureProperty) -and $null -eq $getCurrentStateResult.Ensure) + { + <# + Removing the property Ensure from the comparison since the method + GetCurrentState() did not return it, and we don't know the current + state value until the method Compare() has run. + #> + $ignoreProperty += 'Ensure' + } + <# Returns all enforced properties not in desires state, or $null if all enforced properties are in desired state. #> - $propertiesNotInDesiredState = $this.Compare($getCurrentStateResult) + $propertiesNotInDesiredState = $this.Compare($getCurrentStateResult, $ignoreProperty) <# Return the correct value for Ensure property if it hasn't been already set by GetCurrentState(). #> - if (($this | Test-ResourceHasEnsureProperty) -and -not $getCurrentStateResult.Ensure) + if (($this | Test-ResourceHasEnsureProperty) -and $null -eq $getCurrentStateResult.Ensure) { - if ($propertiesNotInDesiredState) + if ($propertiesNotInDesiredState -or (-not $propertiesNotInDesiredState -and $this.Ensure -eq [Ensure]::Absent)) { $dscResourceObject.Ensure = [Ensure]::Absent } @@ -79,13 +94,40 @@ class ResourceBase } } + # TODO: And only if $getCurrentStateResult no already contain key Reasons if ($propertiesNotInDesiredState) { foreach ($property in $propertiesNotInDesiredState) { + if ($property.ExpectedValue -is [System.Enum]) + { + # TODO: Maybe we just convert the advanced types to JSON and do not convert other types? + # Test that on SqlDatabasePermission + + # Return the string representation of the value so that conversion to json is correct. + $propertyExpectedValue = $property.ExpectedValue.ToString() + } + else + { + $propertyExpectedValue = $property.ExpectedValue + } + + if ($property.ActualValue -is [System.Enum]) + { + # TODO: Maybe we just convert the advanced types to JSON and do not convert other types? + # Test that on SqlDatabasePermission + + # Return the string representation of the value so that conversion to json is correct. + $propertyActualValue = $property.ActualValue.ToString() + } + else + { + $propertyActualValue = $property.ActualValue + } + $dscResourceObject.Reasons += [Reason] @{ Code = '{0}:{0}:{1}' -f $this.GetType(), $property.Property - Phrase = 'The property {0} should be {1}, but was {2}' -f $property.Property, ($property.ExpectedValue | ConvertTo-Json -Compress), ($property.ActualValue | ConvertTo-Json -Compress) + Phrase = 'The property {0} should be {1}, but was {2}' -f $property.Property, ($propertyExpectedValue | ConvertTo-Json -Compress), ($propertyActualValue | ConvertTo-Json -Compress) } Write-Verbose -Verbose -Message ($this.Reasons | Out-String) @@ -176,10 +218,16 @@ class ResourceBase { $currentState = $this.Get() | ConvertFrom-DscResourceInstance - return $this.Compare($currentState) + return $this.Compare($currentState, @()) } - hidden [System.Collections.Hashtable[]] Compare([System.Collections.Hashtable] $currentState) + # TODO: remove this if not needed. + # hidden [System.Collections.Hashtable[]] Compare([System.Collections.Hashtable] $currentState) + # { + # return $this.Compare($currentState, @()) + # } + + hidden [System.Collections.Hashtable[]] Compare([System.Collections.Hashtable] $currentState, [System.String[]] $excludeProperties) { $desiredState = $this | Get-DesiredStateProperty @@ -187,7 +235,7 @@ class ResourceBase CurrentValues = $currentState DesiredValues = $desiredState Properties = $desiredState.Keys - ExcludeProperties = $this.notEnforcedProperties + ExcludeProperties = $excludeProperties + $this.notEnforcedProperties IncludeValue = $true } @@ -197,7 +245,6 @@ class ResourceBase #> return (Compare-DscParameterState @CompareDscParameterState) } - # Returns a hashtable containing all properties that should be enforced. <# TODO: This should be a private function, e.g ConvertFrom-CompareHashtable, diff --git a/tests/Unit/Classes/ResourceBase.Tests.ps1 b/tests/Unit/Classes/ResourceBase.Tests.ps1 index a1ea41f52..4f7f70788 100644 --- a/tests/Unit/Classes/ResourceBase.Tests.ps1 +++ b/tests/Unit/Classes/ResourceBase.Tests.ps1 @@ -99,13 +99,21 @@ Describe 'ResourceBase\Get()' -Tag 'Get' { # Only return localized strings for this class name. @('ResourceBase') } + } - <# - Must use a here-string because we need to pass 'using' which must be - first in a scriptblock, but if it is outside the here-string then - PowerShell will fail to parse the test script. - #> - $inModuleScopeScriptBlock = @' + Context 'When the configuration should be present' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' using module SqlServerDsc class MyMockResource : ResourceBase @@ -114,10 +122,18 @@ class MyMockResource : ResourceBase [System.String] $MyResourceKeyProperty1 + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + [DscProperty()] [System.String] $MyResourceProperty2 + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { return @{ @@ -130,22 +146,542 @@ class MyMockResource : ResourceBase $script:mockResourceBaseInstance = [MyMockResource]::new() '@ - InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + $mockResourceBaseInstance.MyResourceProperty2 = 'MyValue2' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Present) + $getResult.Reasons | Should -BeNullOrEmpty + } + } } - It 'Should have correctly instantiated the resource class' { - InModuleScope -ScriptBlock { - $mockResourceBaseInstance | Should -Not -BeNullOrEmpty - $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + Context 'When the configuration should be absent' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = $null + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Ensure = [Ensure]::Absent + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -BeNullOrEmpty + $getResult.Ensure | Should -Be ([Ensure]::Absent) + $getResult.Reasons | Should -BeNullOrEmpty + } } } - It 'Should return the correct values for the properties' { - InModuleScope -ScriptBlock { - $getResult = $mockResourceBaseInstance.Get() + Context 'When returning Ensure property from method GetCurrentState()' { + Context 'When the configuration should be present' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc - $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' - $getResult.MyResourceProperty2 | Should -Be 'MyValue2' +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + Ensure = [Ensure]::Present + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = 'MyValue2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + $mockResourceBaseInstance.MyResourceProperty2 = 'MyValue2' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Present) + $getResult.Reasons | Should -BeNullOrEmpty + } + } + } + + Context 'When the configuration should be absent' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + Ensure = [Ensure]::Absent + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = $null + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Ensure = [Ensure]::Absent + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -BeNullOrEmpty + $getResult.Ensure | Should -Be ([Ensure]::Absent) + $getResult.Reasons | Should -BeNullOrEmpty + } + } + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + } + + Context 'When the configuration should be present' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = 'MyValue2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + $mockResourceBaseInstance.MyResourceProperty2 = 'NewValue2' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Absent) + + $getResult.Reasons | Should -HaveCount 1 + $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceProperty2' + $getResult.Reasons[0].Phrase | Should -Be 'The property MyResourceProperty2 should be "NewValue2", but was "MyValue2"' + } + } + } + + Context 'When the configuration should be absent' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = 'MyValue2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Ensure = [Ensure]::Absent + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Present) + + $getResult.Reasons | Should -HaveCount 1 + $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceProperty2' + $getResult.Reasons[0].Phrase | Should -Be 'The property MyResourceProperty2 should be "NewValue2", but was "MyValue2"' + } + } + } + + Context 'When returning Ensure property from method GetCurrentState()' { + Context 'When the configuration should be present' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + Ensure = [Ensure]::Absent + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = 'MyValue2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + $mockResourceBaseInstance.MyResourceProperty2 = 'NewValue2' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Absent) + + $getResult.Reasons | Should -HaveCount 2 + + # The order in the array was sometimes different so could not use array index ($getResult.Reasons[0]). + $getResult.Reasons.Code | Should -Contain 'MyMockResource:MyMockResource:MyResourceProperty2' + $getResult.Reasons.Code | Should -Contain 'MyMockResource:MyMockResource:Ensure' + $getResult.Reasons.Phrase | Should -Contain 'The property MyResourceProperty2 should be "NewValue2", but was "MyValue2"' + $getResult.Reasons.Phrase | Should -Contain 'The property Ensure should be "Present", but was "Absent"' + } + } + } + + Context 'When the configuration should be absent' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + Ensure = [Ensure]::Present + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = 'MyValue2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Ensure = [Ensure]::Absent + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Present) + + $getResult.Reasons | Should -HaveCount 1 + + $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:Ensure' + $getResult.Reasons[0].Phrase | Should -Be 'The property Ensure should be "Absent", but was "Present"' + } + } } } } From a4e0881cef501506bce4a7c88aebcf07b911a8e3 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 7 Jul 2022 22:24:22 +0200 Subject: [PATCH 30/98] Fix unit tests --- source/Classes/002.DatabasePermission.ps1 | 35 +++++- source/Classes/010.ResourceBase.ps1 | 8 +- .../Public/Get-SqlDscDatabasePermission.ps1 | 9 +- .../Unit/Classes/DatabasePermission.Tests.ps1 | 108 ++++++++++++++++ tests/Unit/Classes/ResourceBase.Tests.ps1 | 118 ++++++++++++++---- .../Classes/SqlDatabasePermission.Tests.ps1 | 44 ++++--- 6 files changed, 268 insertions(+), 54 deletions(-) diff --git a/source/Classes/002.DatabasePermission.ps1 b/source/Classes/002.DatabasePermission.ps1 index aa46169c7..3ea7594d3 100644 --- a/source/Classes/002.DatabasePermission.ps1 +++ b/source/Classes/002.DatabasePermission.ps1 @@ -15,7 +15,7 @@ detect missing properties during compilation. The Key property is evaluate during runtime so that no two states are enforcing the same permission. #> -class DatabasePermission +class DatabasePermission : System.IEquatable[Object] { [DscProperty(Key)] [ValidateSet('Grant', 'GrantWithGrant', 'Deny')] @@ -26,4 +26,37 @@ class DatabasePermission [DscProperty(Mandatory)] [System.String[]] $Permission + + [System.Boolean] Equals([System.Object] $object) + { + $isEqual = $false + + if ($object -is $this.GetType()) + { + if ($this.Grant -eq $object.Grant) + { + if (-not (Compare-Object -ReferenceObject $this.Permission -DifferenceObject $object.Permission)) + { + $isEqual = $true + } + } + } + else + { + <# + TODO: Not sure how to handle [DatabasePermission[]], this was meant to + throw for example if the right side was of the comparison was a + [String]. But this would also throw if the the left side was + [DatabasePermission] and the right side was [DatabasePermission[]]. + For now it returns $false if type is not [DatabasePermission] + on both sides of the comparison. This can be the correct way + since if moving [DatabasePermission[]] to the left side and + the [DatabasePermission] to the right side, then the left side + array is filtered with the matching values on the right side. + #> + #throw ('Invalid type in comparison. Expected type [{0}], but the type was [{1}].' -f $this.GetType().FullName, $object.GetType().FullName) + } + + return $isEqual + } } diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index 6cf320fd6..b3ca698bf 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -84,10 +84,14 @@ class ResourceBase #> if (($this | Test-ResourceHasEnsureProperty) -and $null -eq $getCurrentStateResult.Ensure) { - if ($propertiesNotInDesiredState -or (-not $propertiesNotInDesiredState -and $this.Ensure -eq [Ensure]::Absent)) + if (($propertiesNotInDesiredState -and $this.Ensure -eq [Ensure]::Present) -or (-not $propertiesNotInDesiredState -and $this.Ensure -eq [Ensure]::Absent)) { $dscResourceObject.Ensure = [Ensure]::Absent } + elseif ($propertiesNotInDesiredState -and $this.Ensure -eq [Ensure]::Absent) + { + $dscResourceObject.Ensure = [Ensure]::Present + } else { $dscResourceObject.Ensure = [Ensure]::Present @@ -130,7 +134,7 @@ class ResourceBase Phrase = 'The property {0} should be {1}, but was {2}' -f $property.Property, ($propertyExpectedValue | ConvertTo-Json -Compress), ($propertyActualValue | ConvertTo-Json -Compress) } - Write-Verbose -Verbose -Message ($this.Reasons | Out-String) + Write-Verbose -Verbose -Message ($dscResourceObject.Reasons | Out-String) } } diff --git a/source/Public/Get-SqlDscDatabasePermission.ps1 b/source/Public/Get-SqlDscDatabasePermission.ps1 index 9364f1857..2f8257866 100644 --- a/source/Public/Get-SqlDscDatabasePermission.ps1 +++ b/source/Public/Get-SqlDscDatabasePermission.ps1 @@ -17,10 +17,7 @@ .EXAMPLE $serverInstance = Connect-SqlDscDatabaseEngine - Get-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' - - Connects to the default instance on the current machine and returns the - database permission information for the specified principal. + $permission = Get-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' .NOTES This command excludes fixed roles like _db_datareader_ by default, and will @@ -57,9 +54,9 @@ function Get-SqlDscDatabasePermission $sqlDatabaseObject = $null - if ($sqlServerObject.Databases) + if ($ServerObject.Databases) { - $sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName] + $sqlDatabaseObject = $ServerObject.Databases[$DatabaseName] } if ($sqlDatabaseObject) diff --git a/tests/Unit/Classes/DatabasePermission.Tests.ps1 b/tests/Unit/Classes/DatabasePermission.Tests.ps1 index 55b15c3ca..a7111f2f9 100644 --- a/tests/Unit/Classes/DatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/DatabasePermission.Tests.ps1 @@ -73,4 +73,112 @@ Describe 'DatabasePermission' -Tag 'DatabasePermission' { $mockDatabasePermissionInstance.Permission = 'select' } } + + Context 'When comparing two objects' { + # TODO: See comment in code regarding the code this test was suppose to cover. + # Context 'When the object to compare against is the wrong type' { + # It 'Should throw an error on compare' { + # InModuleScope -ScriptBlock { + # $databasPermissionInstance = [DatabasePermission]::new() + + # $databasPermissionInstance.State = 'Grant' + # $databasPermissionInstance.Permission = 'select' + + # # Must escape the brackets with ` for expected message comparison to work. + # { $databasPermissionInstance -eq 'invalid type' } | + # Should -Throw -ExpectedMessage 'Invalid type in comparison. Expected type `[DatabasePermission`], but the type was `[System.String`].' + # } + # } + # } + + Context 'When both objects are equal' { + Context 'When property Permission has a single value' { + It 'Should return $true' { + InModuleScope -ScriptBlock { + $databasPermissionInstance1 = [DatabasePermission]::new() + + $databasPermissionInstance1.State = 'Grant' + $databasPermissionInstance1.Permission = 'select' + + $databasPermissionInstance2 = [DatabasePermission]::new() + + $databasPermissionInstance2.State = 'Grant' + $databasPermissionInstance2.Permission = 'select' + + $databasPermissionInstance1 -eq $databasPermissionInstance2 | Should -BeTrue + } + } + } + + Context 'When property Permission has a multiple values' { + It 'Should return $true' { + InModuleScope -ScriptBlock { + $databasPermissionInstance1 = [DatabasePermission]::new() + + $databasPermissionInstance1.State = 'Grant' + $databasPermissionInstance1.Permission = @('select', 'update') + + $databasPermissionInstance2 = [DatabasePermission]::new() + + $databasPermissionInstance2.State = 'Grant' + $databasPermissionInstance2.Permission = @('select', 'update') + + $databasPermissionInstance1 -eq $databasPermissionInstance2 | Should -BeTrue + } + } + } + } + + Context 'When object has different value for property State' { + It 'Should instantiate two objects' { + $script:mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { + $databasPermissionInstance = [DatabasePermission]::new() + + $databasPermissionInstance.State = 'Deny' + $databasPermissionInstance.Permission = 'select' + + return $databasPermissionInstance + } + + $script:mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { + $databasPermissionInstance = [DatabasePermission]::new() + + $databasPermissionInstance.State = 'Grant' + $databasPermissionInstance.Permission = 'select' + + return $databasPermissionInstance + } + } + + It 'Should return $false' { + $mockDatabasePermissionInstance1 -eq $mockDatabasePermissionInstance2 | Should -BeFalse + } + } + + Context 'When object has different value for property Permission' { + It 'Should instantiate two objects' { + $script:mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { + $databasPermissionInstance = [DatabasePermission]::new() + + $databasPermissionInstance.State = 'Grant' + $databasPermissionInstance.Permission = 'select' + + return $databasPermissionInstance + } + + $script:mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { + $databasPermissionInstance = [DatabasePermission]::new() + + $databasPermissionInstance.State = 'Grant' + $databasPermissionInstance.Permission = 'update' + + return $databasPermissionInstance + } + } + + It 'Should return $false' { + $mockDatabasePermissionInstance1 -eq $mockDatabasePermissionInstance2 | Should -BeFalse + } + } + } } diff --git a/tests/Unit/Classes/ResourceBase.Tests.ps1 b/tests/Unit/Classes/ResourceBase.Tests.ps1 index 4f7f70788..87bc5eeae 100644 --- a/tests/Unit/Classes/ResourceBase.Tests.ps1 +++ b/tests/Unit/Classes/ResourceBase.Tests.ps1 @@ -395,13 +395,14 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() } Context 'When the configuration should be present' { - BeforeAll { - <# - Must use a here-string because we need to pass 'using' which must be - first in a scriptblock, but if it is outside the here-string then - PowerShell will fail to parse the test script. - #> - $inModuleScopeScriptBlock = @' + Context 'When a non-mandatory parameter is not in desired state' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' using module SqlServerDsc class MyMockResource : ResourceBase @@ -434,30 +435,96 @@ class MyMockResource : ResourceBase $script:mockResourceBaseInstance = [MyMockResource]::new() '@ - InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) - } + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } - It 'Should have correctly instantiated the resource class' { - InModuleScope -ScriptBlock { - $mockResourceBaseInstance | Should -Not -BeNullOrEmpty - $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + $mockResourceBaseInstance.MyResourceProperty2 = 'NewValue2' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Absent) + + $getResult.Reasons | Should -HaveCount 1 + $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceProperty2' + $getResult.Reasons[0].Phrase | Should -Be 'The property MyResourceProperty2 should be "NewValue2", but was "MyValue2"' + } } } - It 'Should return the correct values for the properties' { - InModuleScope -ScriptBlock { - $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' - $mockResourceBaseInstance.MyResourceProperty2 = 'NewValue2' + Context 'When a mandatory parameter is not in desired state' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc - $getResult = $mockResourceBaseInstance.Get() +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 - $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' - $getResult.MyResourceProperty2 | Should -Be 'MyValue2' - $getResult.Ensure | Should -Be ([Ensure]::Absent) + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present - $getResult.Reasons | Should -HaveCount 1 - $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceProperty2' - $getResult.Reasons[0].Phrase | Should -Be 'The property MyResourceProperty2 should be "NewValue2", but was "MyValue2"' + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + MyResourceKeyProperty1 = $null + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -BeNullOrEmpty + $getResult.Ensure | Should -Be ([Ensure]::Absent) + + $getResult.Reasons | Should -HaveCount 1 + $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceKeyProperty1' + $getResult.Reasons[0].Phrase | Should -Be 'The property MyResourceKeyProperty1 should be "MyValue1", but was null' + } } } } @@ -521,6 +588,7 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() InModuleScope -ScriptBlock { $mockResourceBaseInstance.Ensure = [Ensure]::Absent $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + $mockResourceBaseInstance.MyResourceProperty2 = $null $getResult = $mockResourceBaseInstance.Get() @@ -530,7 +598,7 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() $getResult.Reasons | Should -HaveCount 1 $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceProperty2' - $getResult.Reasons[0].Phrase | Should -Be 'The property MyResourceProperty2 should be "NewValue2", but was "MyValue2"' + $getResult.Reasons[0].Phrase | Should -Be 'The property MyResourceProperty2 should be "", but was "MyValue2"' } } } diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index 3997ea4e9..b4fb6461a 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -87,20 +87,22 @@ Describe 'SqlDatabasePermission' { } Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { - BeforeAll { - InModuleScope -ScriptBlock { - $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - } - } - } - Context 'When the system is in the desired state' { - Context 'When the desired permission does exist' { + Context 'When the desired permission should exist' { BeforeAll { InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + ) + } + <# This mocks the method GetCurrentState(). @@ -111,7 +113,6 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { $script:mockSqlDatabasePermissionInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Ensure = 'Present' InstanceName = 'NamedInstance' DatabaseName = 'MockDatabaseName' Name = 'MockUserName' @@ -122,8 +123,6 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { Permission = @('Connect') } ) - Credential = $null - Reasons = $null } } } @@ -149,9 +148,17 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { } } - Context 'When the desired permission does not exist' { + Context 'When the desired permission should not exist' { BeforeAll { InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Ensure = 'Absent' + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @() + } + <# This mocks the method GetCurrentState(). @@ -162,20 +169,17 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { $script:mockSqlDatabasePermissionInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Ensure = 'Absent' InstanceName = 'NamedInstance' DatabaseName = 'MockDatabaseName' Name = 'MockUserName' ServerName = 'localhost' Permission = [DatabasePermission[]] @() - Credential = $null - Reasons = $null } } } } - It 'Should return the state as present' { + It 'Should return the state as absent' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.Get() @@ -189,7 +193,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' - $currentState.Permission| Should -BeNullOrEmpty + $currentState.Permission | Should -BeNullOrEmpty } } } From 9c052ec3359c1b17e25b5cde40d3a6fc6fae9756 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 8 Jul 2022 15:08:11 +0200 Subject: [PATCH 31/98] Fix code --- source/Classes/010.ResourceBase.ps1 | 75 ++++----- source/Classes/020.SqlDatabasePermission.ps1 | 20 ++- source/Private/ConvertFrom-CompareResult.ps1 | 41 +++++ tests/Unit/Classes/ResourceBase.Tests.ps1 | 63 ++++---- .../Classes/SqlDatabasePermission.Tests.ps1 | 144 ++++++++++++++++-- .../ConvertFrom-CompareResult.Tests.ps1 | 105 +++++++++++++ 6 files changed, 361 insertions(+), 87 deletions(-) create mode 100644 source/Private/ConvertFrom-CompareResult.ps1 create mode 100644 tests/Unit/Private/ConvertFrom-CompareResult.Tests.ps1 diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index b3ca698bf..7d9e881ab 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -49,6 +49,7 @@ class ResourceBase $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) + # Set values returned from the derived class' GetCurrentState(). foreach ($propertyName in $this.PSObject.Properties.Name) { if ($propertyName -in @($getCurrentStateResult.Keys)) @@ -57,9 +58,30 @@ class ResourceBase } } - # TODO: If $getCurrentStateResult does not contain Ensure (or null) then - # we must remove Ensure from the comparison by calling a Compare() override - # that takes an array with properties that should be ignored. + # Set key property values unless it was returned from the derived class' GetCurrentState(). + foreach ($propertyName in $keyProperty.Keys) + { + if ($propertyName -notin @($getCurrentStateResult.Keys)) + { + # Add the key value to the instance to be returned. + $dscResourceObject.$propertyName = $this.$propertyName + + <# + If the key property should be enforced, add it to the current + state hashtable so Compare() will enforce it. The property will + always be in desired state since it is the desired state value + that is set as the current state value. But this will help so + that a derived class' method GetCurrentState() does not need + to return the key property values if the properties has not + been added to the class property '$this.notEnforcedProperties'. + #> + if ($propertyName -notin $this.notEnforcedProperties) + { + $getCurrentStateResult.$propertyName = $this.$propertyName + } + } + } + $ignoreProperty = @() if (($this | Test-ResourceHasEnsureProperty) -and $null -eq $getCurrentStateResult.Ensure) @@ -144,10 +166,7 @@ class ResourceBase [void] Set() { - # Get all key properties. - $keyProperty = $this | Get-KeyProperty - - Write-Verbose -Verbose -Message ($this.localizedData.SetDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + Write-Verbose -Verbose -Message ($this.localizedData.SetDesiredState -f $this.GetType().Name, ($this | Get-KeyProperty | ConvertTo-Json -Compress)) $this.Assert() @@ -159,11 +178,12 @@ class ResourceBase if ($propertiesNotInDesiredState) { - $propertiesToModify = $this.GetDesiredStateForSplatting($propertiesNotInDesiredState) + $propertiesToModify = $propertiesNotInDesiredState | ConvertFrom-CompareResult - $propertiesToModify.Keys | ForEach-Object -Process { - Write-Verbose -Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_) - } + $propertiesToModify.Keys | + ForEach-Object -Process { + Write-Verbose -Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_) + } <# Call the Modify() method with the properties that should be enforced @@ -179,10 +199,7 @@ class ResourceBase [System.Boolean] Test() { - # Get all key properties. - $keyProperty = $this | Get-KeyProperty - - Write-Verbose -Verbose -Message ($this.localizedData.TestDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + Write-Verbose -Verbose -Message ($this.localizedData.TestDesiredState -f $this.GetType().Name, ($this | Get-KeyProperty | ConvertTo-Json -Compress)) $this.Assert() @@ -225,12 +242,13 @@ class ResourceBase return $this.Compare($currentState, @()) } - # TODO: remove this if not needed. - # hidden [System.Collections.Hashtable[]] Compare([System.Collections.Hashtable] $currentState) - # { - # return $this.Compare($currentState, @()) - # } + <# + Returns a hashtable containing all properties that should be enforced and + are not in desired state, or $null if all enforced properties are in + desired state. + This method should normally not be overridden. + #> hidden [System.Collections.Hashtable[]] Compare([System.Collections.Hashtable] $currentState, [System.String[]] $excludeProperties) { $desiredState = $this | Get-DesiredStateProperty @@ -239,7 +257,7 @@ class ResourceBase CurrentValues = $currentState DesiredValues = $desiredState Properties = $desiredState.Keys - ExcludeProperties = $excludeProperties + $this.notEnforcedProperties + ExcludeProperties = ($excludeProperties + $this.notEnforcedProperties) | Select-Object -Unique IncludeValue = $true } @@ -249,21 +267,6 @@ class ResourceBase #> return (Compare-DscParameterState @CompareDscParameterState) } - # Returns a hashtable containing all properties that should be enforced. - <# - TODO: This should be a private function, e.g ConvertFrom-CompareHashtable, - that could have a [Switch] property 'NameAndExpectedValue' - #> - hidden [System.Collections.Hashtable] GetDesiredStateForSplatting([System.Collections.Hashtable[]] $Properties) - { - $desiredState = @{} - - $Properties | ForEach-Object -Process { - $desiredState[$_.Property] = $_.ExpectedValue - } - - return $desiredState - } # This method should normally not be overridden. hidden [void] Assert() diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 91d37d655..8b82a5c97 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -176,9 +176,25 @@ class SqlDatabasePermission : ResourceBase #> hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { - # The property Ensure will be handled by the base class. + $currentStateCredential = $null + + if ($this.Credential) + { + <# + TODO: This does not work, Get() will return an empty PSCredential-object. + Using MOF-based resource variant does not work either as it throws + an error: https://github.com/dsccommunity/ActiveDirectoryDsc/blob/b2838d945204e1153cc3cbfca1a3d90671e0a61c/source/Modules/ActiveDirectoryDsc.Common/ActiveDirectoryDsc.Common.psm1#L1834-L1856 + #> + $currentStateCredential = [PSCredential]::new( + $this.Credential.UserName, + [SecureString]::new() + ) + } + + # The property Ensure and key properties will be handled by the base class. $currentState = @{ - Permission = [DatabasePermission[]] @() + Credential = $currentStateCredential + Permission = [DatabasePermission[]] @() } $connectSqlDscDatabaseEngineParameters = @{ diff --git a/source/Private/ConvertFrom-CompareResult.ps1 b/source/Private/ConvertFrom-CompareResult.ps1 new file mode 100644 index 000000000..eaf799c5b --- /dev/null +++ b/source/Private/ConvertFrom-CompareResult.ps1 @@ -0,0 +1,41 @@ +<# + .SYNOPSIS + Returns a hashtable with property name and their expected value. + + .PARAMETER CompareResult + The result from Compare-DscParameterState. + + .EXAMPLE + ConvertFrom-CompareResult -CompareResult (Compare-DscParameterState) + + .OUTPUTS + [System.Collections.Hashtable] +#> +function ConvertFrom-CompareResult +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Collections.Hashtable[]] + $CompareResult + ) + + begin + { + $returnHashtable = @{} + } + + process + { + $CompareResult | ForEach-Object -Process { + $returnHashtable[$_.Property] = $_.ExpectedValue + } + } + + end + { + return $returnHashtable + } +} diff --git a/tests/Unit/Classes/ResourceBase.Tests.ps1 b/tests/Unit/Classes/ResourceBase.Tests.ps1 index 87bc5eeae..2940774e3 100644 --- a/tests/Unit/Classes/ResourceBase.Tests.ps1 +++ b/tests/Unit/Classes/ResourceBase.Tests.ps1 @@ -134,10 +134,25 @@ class MyMockResource : ResourceBase [Reason[]] $Reasons + <# + This will test so that a key value do not need to be enforced, and still + be returned by Get(). + #> + MyMockResource() : base () + { + # These properties will not be enforced. + $this.notEnforcedProperties = @( + 'MyResourceKeyProperty1' + ) + } + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { + <# + This does not return the key property that is not being enforce, to let + the base class' method Get() return that value. + #> return @{ - MyResourceKeyProperty1 = 'MyValue1' MyResourceProperty2 = 'MyValue2' } } @@ -204,10 +219,20 @@ class MyMockResource : ResourceBase [Reason[]] $Reasons + <# + Tests to enforce a key property even if we do not return the key property value + from the method GetCurrentState. + #> + MyMockResource() : base () + { + # Test not to add the key property to the list of properties that are not enforced. + $this.notEnforcedProperties = @() + } + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { + # This does not return the key property, to let base class handle that. return @{ - MyResourceKeyProperty1 = 'MyValue1' MyResourceProperty2 = $null } } @@ -1039,40 +1064,6 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() } } -Describe 'ResourceBase\GetDesiredStateForSplatting()' -Tag 'GetDesiredStateForSplatting' { - BeforeAll { - $mockResourceBaseInstance = InModuleScope -ScriptBlock { - [ResourceBase]::new() - } - - $mockProperties = @( - @{ - Property = 'MyResourceProperty1' - ExpectedValue = 'MyNewValue1' - ActualValue = 'MyValue1' - }, - @{ - Property = 'MyResourceProperty2' - ExpectedValue = 'MyNewValue2' - ActualValue = 'MyValue2' - } - ) - } - - It 'Should return the correct values in a hashtable' { - $getDesiredStateForSplattingResult = $mockResourceBaseInstance.GetDesiredStateForSplatting($mockProperties) - - $getDesiredStateForSplattingResult | Should -BeOfType [System.Collections.Hashtable] - - $getDesiredStateForSplattingResult.Keys | Should -HaveCount 2 - $getDesiredStateForSplattingResult.Keys | Should -Contain 'MyResourceProperty1' - $getDesiredStateForSplattingResult.Keys | Should -Contain 'MyResourceProperty2' - - $getDesiredStateForSplattingResult.MyResourceProperty1 | Should -Be 'MyNewValue1' - $getDesiredStateForSplattingResult.MyResourceProperty2 | Should -Be 'MyNewValue2' - } -} - Describe 'ResourceBase\Set()' -Tag 'Set' { BeforeAll { Mock -CommandName Assert-Module diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index b4fb6461a..680ac7590 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -36,8 +36,8 @@ BeforeAll { Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../TestHelpers/CommonTestHelper.psm1') - # # Loading mocked classes - # Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') # Load the correct SQL Module stub $script:stubModuleName = Import-SQLModuleStub -PassThru @@ -113,11 +113,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { $script:mockSqlDatabasePermissionInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - InstanceName = 'NamedInstance' - DatabaseName = 'MockDatabaseName' - Name = 'MockUserName' - ServerName = 'localhost' - Permission = [DatabasePermission[]] @( + Permission = [DatabasePermission[]] @( [DatabasePermission] @{ State = 'Grant' Permission = @('Connect') @@ -136,7 +132,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { $currentState.InstanceName | Should -Be 'NamedInstance' $currentState.DatabaseName | Should -Be 'MockDatabaseName' $currentState.Name | Should -Be 'MockUserName' - $currentState.ServerName | Should -Be 'localhost' + $currentState.ServerName | Should -Be (Get-ComputerName) $currentState.Credential | Should -BeNullOrEmpty $currentState.Reasons | Should -BeNullOrEmpty @@ -148,6 +144,70 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { } } + Context 'When the desired permission should exist and using parameter Credential' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Credential = [System.Management.Automation.PSCredential]::new( + 'MyCredentialUserName', + [SecureString]::new() + ) + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + ) + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Credential = $this.Credential + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + ) + } + } + } + } + + It 'Should return the state as present' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.Get() + + $currentState.Ensure | Should -Be 'Present' + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.DatabaseName | Should -Be 'MockDatabaseName' + $currentState.Name | Should -Be 'MockUserName' + $currentState.ServerName | Should -Be (Get-ComputerName) + $currentState.Reasons | Should -BeNullOrEmpty + + $currentState.Credential | Should -BeOfType [System.Management.Automation.PSCredential] + + $currentState.Credential.UserName | Should -Be 'MyCredentialUserName' + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + + $currentState.Permission[0].State | Should -Be 'Grant' + $currentState.Permission[0].Permission | Should -Be 'Connect' + } + } + } + Context 'When the desired permission should not exist' { BeforeAll { InModuleScope -ScriptBlock { @@ -169,10 +229,6 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { $script:mockSqlDatabasePermissionInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - InstanceName = 'NamedInstance' - DatabaseName = 'MockDatabaseName' - Name = 'MockUserName' - ServerName = 'localhost' Permission = [DatabasePermission[]] @() } } @@ -187,7 +243,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { $currentState.InstanceName | Should -Be 'NamedInstance' $currentState.DatabaseName | Should -Be 'MockDatabaseName' $currentState.Name | Should -Be 'MockUserName' - $currentState.ServerName | Should -Be 'localhost' + $currentState.ServerName | Should -Be (Get-ComputerName) $currentState.Credential | Should -BeNullOrEmpty $currentState.Reasons | Should -BeNullOrEmpty @@ -199,3 +255,65 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { } } } + +Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { + Context 'When there are no permission' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission + } + + It 'Should return the state as present' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -BeNullOrEmpty + + # $currentState.Permission[0].State | Should -Be 'Grant' + # $currentState.Permission[0].Permission | Should -Be 'Connect' + } + } + + Context 'When using property Credential' { + It 'Should return the state as present' { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance.Credential = [System.Management.Automation.PSCredential]::new( + 'MyCredentialUserName', + [SecureString]::new() + ) + + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeOfType [System.Management.Automation.PSCredential] + + $currentState.Credential.UserName | Should -Be 'MyCredentialUserName' + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -BeNullOrEmpty + } + } + } + } +} diff --git a/tests/Unit/Private/ConvertFrom-CompareResult.Tests.ps1 b/tests/Unit/Private/ConvertFrom-CompareResult.Tests.ps1 new file mode 100644 index 000000000..b8412d846 --- /dev/null +++ b/tests/Unit/Private/ConvertFrom-CompareResult.Tests.ps1 @@ -0,0 +1,105 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'ConvertFrom-CompareResult' -Tag 'Private' { + Context 'When passing as named parameter' { + It 'Should return the correct values in a hashtable' { + InModuleScope -ScriptBlock { + $mockProperties = @( + @{ + Property = 'MyResourceProperty1' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + }, + @{ + Property = 'MyResourceProperty2' + ExpectedValue = 'MyNewValue2' + ActualValue = 'MyValue2' + } + ) + + $getDesiredStateForSplattingResult = ConvertFrom-CompareResult -CompareResult $mockProperties + + $getDesiredStateForSplattingResult | Should -BeOfType [System.Collections.Hashtable] + + $getDesiredStateForSplattingResult.Keys | Should -HaveCount 2 + $getDesiredStateForSplattingResult.Keys | Should -Contain 'MyResourceProperty1' + $getDesiredStateForSplattingResult.Keys | Should -Contain 'MyResourceProperty2' + + $getDesiredStateForSplattingResult.MyResourceProperty1 | Should -Be 'MyNewValue1' + $getDesiredStateForSplattingResult.MyResourceProperty2 | Should -Be 'MyNewValue2' + } + } + } + + Context 'When passing in the pipeline' { + It 'Should return the correct values in a hashtable' { + InModuleScope -ScriptBlock { + $mockProperties = @( + @{ + Property = 'MyResourceProperty1' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + }, + @{ + Property = 'MyResourceProperty2' + ExpectedValue = 'MyNewValue2' + ActualValue = 'MyValue2' + } + ) + + $getDesiredStateForSplattingResult = $mockProperties | ConvertFrom-CompareResult + + $getDesiredStateForSplattingResult | Should -BeOfType [System.Collections.Hashtable] + + $getDesiredStateForSplattingResult.Keys | Should -HaveCount 2 + $getDesiredStateForSplattingResult.Keys | Should -Contain 'MyResourceProperty1' + $getDesiredStateForSplattingResult.Keys | Should -Contain 'MyResourceProperty2' + + $getDesiredStateForSplattingResult.MyResourceProperty1 | Should -Be 'MyNewValue1' + $getDesiredStateForSplattingResult.MyResourceProperty2 | Should -Be 'MyNewValue2' + } + } + } +} From 9c87a75dc412f1fa694b8322c529dbb690b84900 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 9 Jul 2022 16:20:30 +0200 Subject: [PATCH 32/98] Fix unit test --- .../Classes/SqlDatabasePermission.Tests.ps1 | 263 ++++++++++++++++++ tests/Unit/Stubs/SMO.cs | 9 + 2 files changed, 272 insertions(+) diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index 680ac7590..f0b2ff2b7 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -316,4 +316,267 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { } } } + + Context 'When there are permissions for only state Grant' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + $mockDatabasePermissionInfo = @() + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + return $mockDatabasePermissionInfo + } + } + + It 'Should return the state as present' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 1 + + $currentState.Permission[0].State | Should -Be 'Grant' + $currentState.Permission[0].Permission | Should -Contain 'Connect' + $currentState.Permission[0].Permission | Should -Contain 'Update' + } + } + } + + Context 'When there are permissions for both state Grant and Deny' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + $mockDatabasePermissionInfo = @() + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $false, $true, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Deny' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + return $mockDatabasePermissionInfo + } + } + + It 'Should return the state as present' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 2 + + $currentState.Permission[0].State | Should -Be 'Grant' + $currentState.Permission[0].Permission | Should -Contain 'Connect' + $currentState.Permission[0].Permission | Should -Contain 'Update' + + $currentState.Permission[1].State | Should -Be 'Deny' + $currentState.Permission[1].Permission | Should -Contain 'Select' + } + } + } +} + +Describe 'SqlDatabasePermission\Set()' -Tag 'Set' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + } | + # Mock method Modify which is called by the base method Set(). + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Modify' -Value { + $script:mockMethodModifyCallCount += 1 + } -PassThru + } + } + + BeforeEach { + InModuleScope -ScriptBlock { + $script:mockMethodModifyCallCount = 0 + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } + } + } + + It 'Should not call method Modify()' { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance.Set() + + $script:mockMethodModifyCallCount | Should -Be 0 + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @{ + Property = 'Permission' + ExpectedValue = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect', 'Update') + } + ) + ActualValue = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + ) + } + } + } + } + + It 'Should not call method Modify()' { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance.Set() + + $script:mockMethodModifyCallCount | Should -Be 1 + } + } + } +} + +Describe 'SqlDatabasePermission\Test()' -Tag 'Test' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + } + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance.Test() | Should -BeTrue + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @{ + Property = 'Permission' + ExpectedValue = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect', 'Update') + } + ) + ActualValue = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + ) + } + } + } + } + + It 'Should return $false' { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance.Test() | Should -BeFalse + } + } + } } diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index 3d8dd9c63..71026324f 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -212,8 +212,17 @@ public DatabasePermissionSet( bool connect, bool update ) this.Update = update; } + // Used for testing SqlDatabasePermission + public DatabasePermissionSet( bool connect, bool update, bool select, bool insert ) : this ( connect, update ) + { + this.Select = select; + this.Insert = insert; + } + public bool Connect = false; public bool Update = false; + public bool Select = false; + public bool Insert = false; } // TypeName: Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo From 2358579904792cb1378eaa9b2a37f2422c982b65 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 10 Jul 2022 18:35:37 +0200 Subject: [PATCH 33/98] Fix public function --- azure-pipelines.yml | 2 +- source/Classes/020.SqlDatabasePermission.ps1 | 28 +-- .../Public/Get-SqlDscDatabasePermission.ps1 | 9 +- .../Public/Set-SqlDscDatabasePermission.ps1 | 172 ++++++++++++++++++ .../Public/Test-SqlDscIsDatabasePrincipal.ps1 | 13 ++ source/en-US/SqlServerDsc.strings.psd1 | 8 +- 6 files changed, 216 insertions(+), 16 deletions(-) create mode 100644 source/Public/Set-SqlDscDatabasePermission.ps1 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dc2f4149b..957347fd8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -68,7 +68,7 @@ stages: buildType: 'current' artifactName: $(buildArtifactName) targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' - # This task need to user Windows PowerShell due to a bug in PS7 that cannot + # This task need to use Windows PowerShell due to a bug in PS7 that cannot # find/use class-based DSC resources that uses inheritance, which result in # the examples cannot compile. See the following issue for more information: # https://github.com/dsccommunity/DnsServerDsc/issues/268#issuecomment-918505230 diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 8b82a5c97..286d24728 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -223,10 +223,13 @@ class SqlDatabasePermission : ResourceBase if ($databasePermissionInfo) { - $permissionState = $databasePermissionInfo | ForEach-Object -Process { - # Convert from the type PermissionState to String. - [System.String] $_.PermissionState - } | Select-Object -Unique + $permissionState = @( + $databasePermissionInfo | ForEach-Object -Process { + # Convert from the type PermissionState to String. + [System.String] $_.PermissionState + } | + Select-Object -Unique + ) foreach ($currentPermissionState in $permissionState) { @@ -244,17 +247,18 @@ class SqlDatabasePermission : ResourceBase foreach ($currentPermission in $filteredDatabasePermission) { - $permissionProperty = ( - $currentPermission.PermissionType | - Get-Member -MemberType Property - ).Name + # get the permissions that is set to $true + $permissionProperty = $currentPermission.PermissionType | + Get-Member -MemberType 'Property' | + Select-Object -ExpandProperty 'Name' | + Where-Object -FilterScript { + $currentPermission.PermissionType.$_ + } + foreach ($currentPermissionProperty in $permissionProperty) { - if ($currentPermission.PermissionType."$currentPermissionProperty") - { - $statePermissionResult += $currentPermissionProperty - } + $statePermissionResult += $currentPermissionProperty } } diff --git a/source/Public/Get-SqlDscDatabasePermission.ps1 b/source/Public/Get-SqlDscDatabasePermission.ps1 index 2f8257866..d3d97dafb 100644 --- a/source/Public/Get-SqlDscDatabasePermission.ps1 +++ b/source/Public/Get-SqlDscDatabasePermission.ps1 @@ -17,7 +17,7 @@ .EXAMPLE $serverInstance = Connect-SqlDscDatabaseEngine - $permission = Get-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' + Get-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' .NOTES This command excludes fixed roles like _db_datareader_ by default, and will @@ -25,12 +25,17 @@ If specifying `-ErrorAction 'SilentlyContinue'` then the command will silently ignore if the database (parameter **DatabaseName**) is not present or the - database principal is not present. In such case the command will return `$false`. + database principal is not present. In such case the command will return `$null`. If specifying `-ErrorAction 'Stop'` the command will throw an error if the database or database principal is missing. #> function Get-SqlDscDatabasePermission { + <# + The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error + in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 + When QA test run it loads the stub SMO classes so that the rule passes. + #> [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because Script Analyzer does not understand type even if cast when using comma in return statement')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] [CmdletBinding()] diff --git a/source/Public/Set-SqlDscDatabasePermission.ps1 b/source/Public/Set-SqlDscDatabasePermission.ps1 new file mode 100644 index 000000000..c32e7d6a4 --- /dev/null +++ b/source/Public/Set-SqlDscDatabasePermission.ps1 @@ -0,0 +1,172 @@ +<# + .SYNOPSIS + Returns the current permissions for the database principal. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER DatabaseName + Specifies the database name. + + .PARAMETER Name + Specifies the name of the database principal for which the permissions are + returned. + + .OUTPUTS + None. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + + $setPermission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + Update = $true + } + + Set-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' -State 'Grant' -Permission $setPermission + + .NOTES + This command excludes fixed roles like _db_datareader_ by default, and will + always throw a non-terminating error if a fixed role is specified as **Name**. + + If specifying `-ErrorAction 'SilentlyContinue'` then the command will silently + ignore if the database (parameter **DatabaseName**) is not present or the + database principal is not present. If specifying `-ErrorAction 'Stop'` the + command will throw an error if the database or database principal is missing. + + # TODO: This command should support ShouldProcess and Force parameter. Also, + the ScriptAnalyzer rule should be run on Public functions. + + # TODO: Document the public commands using PlatyPS, might use Sampler/ActiveDirectoryDsc as an example? +#> +function Set-SqlDscDatabasePermission +{ + <# + The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error + in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 + When QA test run it loads the stub SMO classes so that the rule passes. + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because Script Analyzer does not understand type even if cast when using comma in return statement')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] + [CmdletBinding()] + [OutputType()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $DatabaseName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ValidateSet('Grant', 'Deny', 'Revoke')] + [System.String] + $State, + + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] + $Permission, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $WithGrant + ) + + # TODO: Assert properties to WithGrant it only possible for Grant and Revoke + $sqlDatabaseObject = $null + + if ($ServerObject.Databases) + { + $sqlDatabaseObject = $ServerObject.Databases[$DatabaseName] + } + + if ($sqlDatabaseObject) + { + $testSqlDscIsDatabasePrincipalParameters = @{ + ServerObject = $ServerObject + DatabaseName = $DatabaseName + Name = $Name + ExcludeFixedRoles = $true + } + + $isDatabasePrincipal = Test-SqlDscIsDatabasePrincipal @testSqlDscIsDatabasePrincipalParameters + + if ($isDatabasePrincipal) + { + # TODO: Set permissions. + Write-Verbose -Message ( + $script:localizedData.DatabasePermission_ChangePermissionForUser -f $Name, $DatabaseName, $InstanceName + ) + + # Get the permissions names that are set to $true in the DatabasePermissionSet. + $permissionName = $Permission | + Get-Member -MemberType 'Property' | + Select-Object -ExpandProperty 'Name' | + Where-Object -FilterScript { + $setPermission.$_ + } + + switch ($State) + { + 'Grant' + { + Write-Verbose -Message ( + $script:localizedData.DatabasePermission_GrantPermission -f ($permissionName -join ','), $Name + ) + + if ($WithGrant.IsPresent) + { + $sqlDatabaseObject.Grant($Permission, $Name, $true) + } + else + { + $sqlDatabaseObject.Grant($Permission, $Name) + } + } + + 'Deny' + { + Write-Verbose -Message ( + $script:localizedData.DatabasePermission_DenyPermission -f ($permissionName -join ','), $Name + ) + + $sqlDatabaseObject.Deny($Permission, $Name) + } + + 'Revoke' + { + Write-Verbose -Message ( + $script:localizedData.DatabasePermission_RevokePermission -f ($permissionName -join ','), $Name + ) + + if ($WithGrant.IsPresent) + { + $sqlDatabaseObject.Revoke($Permission, $Name, $false, $true) + } + else + { + $sqlDatabaseObject.Revoke($Permission, $Name) + } + } + } + } + else + { + $missingPrincipalMessage = $script:localizedData.DatabasePermission_MissingPrincipal -f $Name, $DatabaseName + + Write-Error -Message $missingPrincipalMessage -Category 'InvalidOperation' -ErrorId 'GSDDP0001' -TargetObject $Name + } + } + else + { + $missingDatabaseMessage = $script:localizedData.DatabasePermission_MissingDatabase -f $DatabaseName + + Write-Error -Message $missingDatabaseMessage -Category 'InvalidOperation' -ErrorId 'GSDDP0002' -TargetObject $DatabaseName + } +} diff --git a/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 b/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 index e0139e589..8fb9ea110 100644 --- a/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 +++ b/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 @@ -24,9 +24,22 @@ .PARAMETER ExcludeApplicationRoles Specifies that fixed application roles should not be evaluated for the specified name. + + .OUTPUTS + [System.Boolean] + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Test-SqlDscIsDatabasePrincipal -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' + #> function Test-SqlDscIsDatabasePrincipal { + <# + The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error + in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 + When QA test run it loads the stub SMO classes so that the rule passes. + #> [CmdletBinding()] [OutputType([System.Boolean])] param diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 15e736d48..23ac49f31 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -6,10 +6,16 @@ #> ConvertFrom-StringData @' - # Get-SqlDscDatabasePermission + # Get-SqlDscDatabasePermission, Set-SqlDscDatabasePermission DatabasePermission_MissingPrincipal = The database principal '{0}' is neither a user, database role (user-defined), or database application role in the database '{1}'. DatabasePermission_MissingDatabase = The database '{0}' cannot be found. + # Set-SqlDscDatabasePermission + DatabasePermission_ChangePermissionForUser = Changing the permission for the user '{0}' in the database '{1}' on the instance '{2}'. + DatabasePermission_GrantPermission = Grant the permissions '{0}' for the principal '{1}'. + DatabasePermission_DenyPermission = Deny the permissions '{0}' for the principal '{1}'. + DatabasePermission_RevokePermission = Revoke the permissions '{0}' for the principal '{1}'. + # Test-SqlDscIsDatabasePrincipal IsDatabasePrincipal_DatabaseMissing = The database '{0}' cannot be found. '@ From a453a893caf251edf3b20246506557d5ac308337 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 11 Jul 2022 13:09:39 +0200 Subject: [PATCH 34/98] Fix examples --- .../SqlDatabasePermission/1-GrantDatabasePermissions.ps1 | 9 ++++++--- .../2-RevokeDatabasePermissions.ps1 | 6 ++++-- .../SqlDatabasePermission/3-DenyDatabasePermissions.ps1 | 9 ++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 index 2e730255c..2d0d03575 100644 --- a/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 +++ b/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 @@ -23,7 +23,8 @@ Configuration Example Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorks' Permission = @( - DatabasePermission { + DatabasePermission + { State = 'Grant' Permission = @('Connect', 'Update') } @@ -39,7 +40,8 @@ Configuration Example Name = 'CONTOSO\SQLUser' DatabaseName = 'AdventureWorks' Permission = @( - DatabasePermission { + DatabasePermission + { State = 'Grant' Permission = @('Connect', 'Update') } @@ -55,7 +57,8 @@ Configuration Example Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorksLT' Permission = @( - DatabasePermission { + DatabasePermission + { State = 'Grant' Permission = @('Connect', 'Update') } diff --git a/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 index 781f64f1c..dc548a889 100644 --- a/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 +++ b/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 @@ -23,7 +23,8 @@ Configuration Example Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorks' Permission = @( - DatabasePermission { + DatabasePermission + { State = 'Grant' Permission = @('Connect', 'Update') } @@ -39,7 +40,8 @@ Configuration Example Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorks' Permission = @( - DatabasePermission { + DatabasePermission + { State = 'Grant' Permission = @('Connect', 'Update') } diff --git a/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 index 7a65d66c4..257093e4f 100644 --- a/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 +++ b/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 @@ -23,7 +23,8 @@ Configuration Example Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorks' Permission = @( - DatabasePermission { + DatabasePermission + { State = 'Deny' Permission = @('Select', 'CreateTable') } @@ -40,7 +41,8 @@ Configuration Example Name = 'CONTOSO\SQLUser' DatabaseName = 'AdventureWorks' Permission = @( - DatabasePermission { + DatabasePermission + { State = 'Deny' Permission = @('Select', 'CreateTable') } @@ -57,7 +59,8 @@ Configuration Example Name = 'CONTOSO\SQLAdmin' DatabaseName = 'AdventureWorksLT' Permission = @( - DatabasePermission { + DatabasePermission + { State = 'Deny' Permission = @('Select', 'CreateTable') } From f99e2fd08b86ef212e1efa749fba334454da6aaa Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 11 Jul 2022 16:14:15 +0200 Subject: [PATCH 35/98] Fix class --- source/Classes/002.DatabasePermission.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Classes/002.DatabasePermission.ps1 b/source/Classes/002.DatabasePermission.ps1 index 3ea7594d3..26a9139dd 100644 --- a/source/Classes/002.DatabasePermission.ps1 +++ b/source/Classes/002.DatabasePermission.ps1 @@ -17,7 +17,7 @@ #> class DatabasePermission : System.IEquatable[Object] { - [DscProperty(Key)] + [DscProperty(Mandatory)] [ValidateSet('Grant', 'GrantWithGrant', 'Deny')] [System.String] $State From 7463533788290669322e24d1f871bb47e918305a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 11 Jul 2022 16:15:32 +0200 Subject: [PATCH 36/98] Fix unsupported RunAsCredential --- source/Classes/020.SqlDatabasePermission.ps1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 286d24728..7d9de72a7 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -103,9 +103,7 @@ } #> -# TODO: Add this if PsDscRunAsCredential is not supported. -#[DscResource(RunAsCredential = 'NotSupported')] -[DscResource()] +[DscResource(RunAsCredential = 'NotSupported')] class SqlDatabasePermission : ResourceBase { [DscProperty(Key)] From a9bd750ed3eda70eb232d8af44c111b890de3f37 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 11 Jul 2022 16:30:40 +0200 Subject: [PATCH 37/98] Fix RunAsCredential not supported --- source/Classes/020.SqlDatabasePermission.ps1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 7d9de72a7..99b88cdb8 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -101,6 +101,16 @@ ) ) } + + .NOTES + The built-in property `PsDscRunAsCredential` is not supported on this DSC + resource as it uses a complex type (another class as the type for a DSC + property). If the property `PsDscRunAsCredential` would be used, then the + complex type will not return any values from Get(). This is most likely an + issue (bug) with _PowerShell DSC_. Instead (as a workaround) the property + `Credential` must be used to specify how to connect to the SQL Server + instance. + #> [DscResource(RunAsCredential = 'NotSupported')] From 2388fd64937c367dcca4048281759969d65f85ed Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 11 Jul 2022 16:35:41 +0200 Subject: [PATCH 38/98] Fix example --- .../SqlDatabasePermission/3-DenyDatabasePermissions.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 index 257093e4f..d2e8dd9ea 100644 --- a/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 +++ b/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 @@ -32,7 +32,7 @@ Configuration Example ServerName = 'sqltest.company.local' InstanceName = 'DSC' - PsDscRunAsCredential = $SqlAdministratorCredential + Credential = $SqlAdministratorCredential } SqlDatabasePermission 'Deny_SqlDatabasePermissions_SQLUser_Db01' @@ -50,7 +50,7 @@ Configuration Example ServerName = 'sqltest.company.local' InstanceName = 'DSC' - PsDscRunAsCredential = $SqlAdministratorCredential + Credential = $SqlAdministratorCredential } SqlDatabasePermission 'Deny_SqlDatabasePermissions_SQLAdmin_Db02' @@ -68,7 +68,7 @@ Configuration Example ServerName = 'sqltest.company.local' InstanceName = 'DSC' - PsDscRunAsCredential = $SqlAdministratorCredential + Credential = $SqlAdministratorCredential } } } From 765c891f9dd5f48a04f7b2e69c957bbdb3723eb3 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 11 Jul 2022 16:48:34 +0200 Subject: [PATCH 39/98] Fix example --- .../SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 index dc548a889..7e0159432 100644 --- a/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 +++ b/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 @@ -38,7 +38,7 @@ Configuration Example { Ensure = 'Absent' Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorks' + DatabaseName = 'AdventureWorksLT' Permission = @( DatabasePermission { From 32bbba872ea077287519af09ae4b6efabe98facd Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 12 Jul 2022 10:22:53 +0200 Subject: [PATCH 40/98] Fix HQRM --- source/Public/Get-SqlDscDatabasePermission.ps1 | 3 ++- source/Public/Set-SqlDscDatabasePermission.ps1 | 3 ++- tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/source/Public/Get-SqlDscDatabasePermission.ps1 b/source/Public/Get-SqlDscDatabasePermission.ps1 index d3d97dafb..5c77c5487 100644 --- a/source/Public/Get-SqlDscDatabasePermission.ps1 +++ b/source/Public/Get-SqlDscDatabasePermission.ps1 @@ -36,7 +36,8 @@ function Get-SqlDscDatabasePermission in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 When QA test run it loads the stub SMO classes so that the rule passes. #> - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because Script Analyzer does not understand type even if cast when using comma in return statement')] + # TODO: Remove this if passing HQRM test (HQRM test does not allow overriding this rule) + #[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because Script Analyzer does not understand type even if cast when using comma in return statement')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] [CmdletBinding()] [OutputType([Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]])] diff --git a/source/Public/Set-SqlDscDatabasePermission.ps1 b/source/Public/Set-SqlDscDatabasePermission.ps1 index c32e7d6a4..9dfe83a27 100644 --- a/source/Public/Set-SqlDscDatabasePermission.ps1 +++ b/source/Public/Set-SqlDscDatabasePermission.ps1 @@ -46,7 +46,8 @@ function Set-SqlDscDatabasePermission in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 When QA test run it loads the stub SMO classes so that the rule passes. #> - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because Script Analyzer does not understand type even if cast when using comma in return statement')] + # TODO: Remove this if passing HQRM test (HQRM test does not allow overriding this rule) + #[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because Script Analyzer does not understand type even if cast when using comma in return statement')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] [CmdletBinding()] [OutputType()] diff --git a/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 b/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 index 2394c3231..65c34063a 100644 --- a/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 +++ b/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 @@ -75,7 +75,7 @@ Describe 'Get-SqlDscDatabasePermission' -Tag 'Public' { } } - Context 'When the database does not exist' { + Context 'When the specified database does not exist among existing database' { BeforeAll { $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { From c5f85f9f08aa9d8c23ed609a355060b7a8e3f91e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 12 Jul 2022 10:53:51 +0200 Subject: [PATCH 41/98] Fix HQRM and unit test --- .../Public/Get-SqlDscDatabasePermission.ps1 | 4 +- .../Public/Set-SqlDscDatabasePermission.ps1 | 2 - .../Set-SqlDscDatabasePermission.Tests.ps1 | 443 ++++++++++++++++++ 3 files changed, 444 insertions(+), 5 deletions(-) create mode 100644 tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 diff --git a/source/Public/Get-SqlDscDatabasePermission.ps1 b/source/Public/Get-SqlDscDatabasePermission.ps1 index 5c77c5487..78dff6544 100644 --- a/source/Public/Get-SqlDscDatabasePermission.ps1 +++ b/source/Public/Get-SqlDscDatabasePermission.ps1 @@ -36,11 +36,9 @@ function Get-SqlDscDatabasePermission in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 When QA test run it loads the stub SMO classes so that the rule passes. #> - # TODO: Remove this if passing HQRM test (HQRM test does not allow overriding this rule) - #[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because Script Analyzer does not understand type even if cast when using comma in return statement')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] [CmdletBinding()] - [OutputType([Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]])] + [OutputType([System.Object[]])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] diff --git a/source/Public/Set-SqlDscDatabasePermission.ps1 b/source/Public/Set-SqlDscDatabasePermission.ps1 index 9dfe83a27..71d09715c 100644 --- a/source/Public/Set-SqlDscDatabasePermission.ps1 +++ b/source/Public/Set-SqlDscDatabasePermission.ps1 @@ -46,8 +46,6 @@ function Set-SqlDscDatabasePermission in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 When QA test run it loads the stub SMO classes so that the rule passes. #> - # TODO: Remove this if passing HQRM test (HQRM test does not allow overriding this rule) - #[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because Script Analyzer does not understand type even if cast when using comma in return statement')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] [CmdletBinding()] [OutputType()] diff --git a/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 b/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 new file mode 100644 index 000000000..dce9f4746 --- /dev/null +++ b/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 @@ -0,0 +1,443 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { + Context 'When no databases exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return $null + } -PassThru -Force + + $script:mockDefaultParameters = @{ + DatabaseName = 'MissingDatabase' + Name = 'KnownUser' + State = 'Grant' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]::new() + } + } + + Context 'When specifying to throw on error' { + BeforeAll { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingDatabase + } + } + + It 'Should throw the correct error' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') + } + } + + Context 'When ignoring the error' { + It 'Should not throw an exception and return $null' { + Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'SilentlyContinue' @mockDefaultParameters | + Should -BeNullOrEmpty + } + } + } + + Context 'When the specified database does not exist among existing database' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'AdventureWorks' -PassThru -Force + } + } -PassThru -Force + + $script:mockDefaultParameters = @{ + DatabaseName = 'MissingDatabase' + Name = 'KnownUser' + State = 'Grant' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]::new() + } + } + + Context 'When specifying to throw on error' { + BeforeAll { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingDatabase + } + } + + It 'Should throw the correct error' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') + } + } + + Context 'When ignoring the error' { + It 'Should not throw an exception and return $null' { + Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'SilentlyContinue' @mockDefaultParameters | + Should -BeNullOrEmpty + } + } + } + + Context 'When the database principal does not exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'AdventureWorks' -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $false + } + + $script:mockDefaultParameters = @{ + DatabaseName = 'AdventureWorks' + Name = 'UnknownUser' + State = 'Grant' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]::new() + } + } + + Context 'When specifying to throw on error' { + BeforeAll { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingPrincipal + } + } + + It 'Should throw the correct error' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'UnknownUser', 'AdventureWorks') + } + } + + Context 'When ignoring the error' { + It 'Should not throw an exception and return $null' { + Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'SilentlyContinue' @mockDefaultParameters | + Should -BeNullOrEmpty + } + } + } + + Context 'When the database principal exist' { + Context 'When permission should be granted' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Grant' -Value { + $script:mockMethodGrantCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Grant' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodGrantCallCount = 0 + } + + It 'Should return the correct values' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodGrantCallCount | Should -Be 1 + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should return the correct values' { + { $mockServerObject | Set-SqlDscDatabasePermission -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodGrantCallCount | Should -Be 1 + } + } + } + + Context 'When permission should be granted and using parameter WithGrant' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Grant' -Value { + param + ( + [Parameter()] + $Permission, + + [Parameter()] + $Name, + + [Parameter()] + $WithGrant + ) + + if ($WithGrant) + { + $script:mockMethodGrantUsingWithGrantCallCount += 1 + } + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Grant' + WithGrant = $true + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodGrantUsingWithGrantCallCount = 0 + } + + It 'Should return the correct values' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodGrantUsingWithGrantCallCount | Should -Be 1 + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should return the correct values' { + { $mockServerObject | Set-SqlDscDatabasePermission -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodGrantUsingWithGrantCallCount | Should -Be 1 + } + } + } + + Context 'When permission should be revoked' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Revoke' -Value { + $script:mockMethodRevokeCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Revoke' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodRevokeCallCount = 0 + } + + It 'Should return the correct values' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodRevokeCallCount | Should -Be 1 + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should return the correct values' { + { $mockServerObject | Set-SqlDscDatabasePermission -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodRevokeCallCount | Should -Be 1 + } + } + } + + Context 'When permission should be revoked and using parameter WithGrant' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Revoke' -Value { + param + ( + [Parameter()] + $Permission, + + [Parameter()] + $Name, + + [Parameter()] + $RevokeGrant, + + [Parameter()] + $Cascade + ) + + if (-not $RevokeGrant -and $Cascade) + { + $script:mockMethodRevokeUsingWithGrantCallCount += 1 + } + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Revoke' + WithGrant = $true + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodRevokeUsingWithGrantCallCount = 0 + } + + It 'Should return the correct values' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodRevokeUsingWithGrantCallCount | Should -Be 1 + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should return the correct values' { + { $mockServerObject | Set-SqlDscDatabasePermission -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodGrantUsingWithGrantCallCount | Should -Be 1 + } + } + } + + Context 'When permission should be denied' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Deny' -Value { + $script:mockMethodDenyCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Deny' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodDenyCallCount = 0 + } + + It 'Should return the correct values' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodDenyCallCount | Should -Be 1 + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should return the correct values' { + { $mockServerObject | Set-SqlDscDatabasePermission -ErrorAction 'Stop' @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodDenyCallCount | Should -Be 1 + } + } + } + } +} From fb89a9689d150c26f0c45498a1542059fa956f6a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 12 Jul 2022 15:13:23 +0200 Subject: [PATCH 42/98] Fix Set --- CONTRIBUTING.md | 9 + source/Classes/020.SqlDatabasePermission.ps1 | 108 +- .../DSC_SqlDatabasePermission.psm1 | 396 ------- .../DSC_SqlDatabasePermission.strings.psd1 | 10 - .../Public/Set-SqlDscDatabasePermission.ps1 | 13 + .../en-US/SqlDatabasePermission.strings.psd1 | 9 +- .../Unit/DSC_SqlDatabasePermission.Tests.ps1 | 1052 ----------------- 7 files changed, 130 insertions(+), 1467 deletions(-) delete mode 100644 source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.psm1 delete mode 100644 source/DSCResources/DSC_SqlDatabasePermission/en-US/DSC_SqlDatabasePermission.strings.psd1 delete mode 100644 tests/Unit/DSC_SqlDatabasePermission.Tests.ps1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4694f569c..8221cb25e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -278,6 +278,14 @@ SQL Server 2016, SQL Server 2017 and SQL Server 2019. When sending in a Pull Request (PR) all example files will be tested so they can be compiled to a .mof file. If the tests find any errors the build will fail. +### Class-based DSC resource + +#### Terminating Error + +A terminating error is an error that prevents the resource to continue further. +If a DSC resource shall throw an terminating error the statement `throw` shall +be used. + ### Commands Commands are publicly exported commands from the module, and the source for @@ -321,6 +329,7 @@ not be used, neither shall the command `Write-Error` be used with the parameter `-ErrorAction `Stop``. Instead the method `$PSCmdlet.ThrowTerminatingError()` shall be used to throw a terminating error. + >**NOTE:** Below output assumes `$ErrorView` is set to `'NormalView'` in the >PowerShell session. diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 99b88cdb8..dda63ee09 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -132,6 +132,7 @@ class SqlDatabasePermission : ResourceBase [System.String] $ServerName = (Get-ComputerName) + # TODO: Should also add IncludePermission and ExcludePermission which should be mutually exclusive from Permission [DscProperty(Mandatory)] [DatabasePermission[]] $Permission @@ -288,13 +289,116 @@ class SqlDatabasePermission : ResourceBase <# Base method Set() call this method with the properties that should be - enforced and that are not in desired state. + enforced and that are not in desired state. It is not called if all + properties are in desired state. #> hidden [void] Modify([System.Collections.Hashtable] $properties) { + # TODO: Remove line below Write-Verbose -Message ($properties | Out-String) -Verbose - #Set-DnsServerDsSetting @properties + $connectSqlDscDatabaseEngineParameters = @{ + ServerName = $this.ServerName + InstanceName = $this.InstanceName + } + + if ($this.Credential) + { + $connectSqlDscDatabaseEngineParameters.Credential = $this.Credential + } + + $serverObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters + + $testSqlDscIsDatabasePrincipalParameters = @{ + ServerObject = $serverObject + DatabaseName = $this.DatabaseName + Name = $this.Name + ExcludeFixedRoles = $true + } + + # This will test wether the database and the principal exist. + $isDatabasePrincipal = Test-SqlDscIsDatabasePrincipal @testSqlDscIsDatabasePrincipalParameters + + if ($isDatabasePrincipal) + { + if ($properties.ContainsKey('Permission')) + { + # Write-Verbose -Message ( + # $this.localizedData.ChangePermissionForUser -f @( + # $this.Name, + # $this.DatabaseName, + # $this.InstanceName + # ) + # ) + + foreach ($currentPermission in $properties.Permission) + { + try + { + $permissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + + foreach ($permissionName in $currentPermission.Permission) + { + $permissionSet.$permissionName = $true + } + + $setSqlDscDatabasePermissionParameters = @{ + ServerObject = $serverObject + DatabaseName = $this.DatabaseName + Name = $this.Name + Permission = $permissionSet + } + + switch ($this.Ensure) + { + 'Present' + { + switch ($currentPermission.State) + { + 'GrantWithGrant' + { + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Grant' -WithGrant + } + + default + { + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State $currentPermission.State + } + } + } + + 'Absent' + { + if ($currentPermission.State -eq 'GrantWithGrant') + { + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Revoke' -WithGrant + } + else + { + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Revoke' + } + } + } + } + catch + { + $errorMessage = $this.localizedData.FailedToSetPermissionDatabase -f $this.Name, $this.DatabaseName + + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + } + } + else + { + $missingPrincipalMessage = $this.localizedData.NameIsMissing -f @( + $properties.Name, + $properties.DatabaseName, + $properties.InstanceName + ) + + New-InvalidOperationException -Message $missingPrincipalMessage + } } <# diff --git a/source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.psm1 b/source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.psm1 deleted file mode 100644 index 625f070a1..000000000 --- a/source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.psm1 +++ /dev/null @@ -1,396 +0,0 @@ -$script:sqlServerDscHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\SqlServerDsc.Common' -$script:resourceHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common' - -Import-Module -Name $script:sqlServerDscHelperModulePath -Import-Module -Name $script:resourceHelperModulePath - -$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' - -<# - .SYNOPSIS - Returns the current permissions for the user in the database. - - .PARAMETER DatabaseName - This is the SQL database - - .PARAMETER Name - This is the name of the SQL login for the permission set. - - .PARAMETER PermissionState - This is the state of permission set. Valid values are 'Grant' or 'Deny'. - - .PARAMETER Permissions - This is a list that represents a SQL Server set of database permissions. - - .PARAMETER ServerName - This is the SQL Server for the database. Default value is the current - computer name. - - .PARAMETER InstanceName - This is the SQL instance for the database. -#> -function Get-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $DatabaseName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter(Mandatory = $true)] - [ValidateSet('Grant', 'Deny', 'GrantWithGrant')] - [System.String] - $PermissionState, - - [Parameter(Mandatory = $true)] - [System.String[]] - $Permissions, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ServerName = (Get-ComputerName), - - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName - ) - - Write-Verbose -Message ( - $script:localizedData.GetDatabasePermission -f $Name, $DatabaseName, $InstanceName - ) - - $returnValue = @{ - Ensure = 'Absent' - ServerName = $ServerName - InstanceName = $InstanceName - DatabaseName = $DatabaseName - Name = $Name - PermissionState = $PermissionState - Permissions = @() - } - - $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName - - if ($sqlServerObject) - { - if ($sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName]) - { - $databasePermissionInfo = $sqlDatabaseObject.EnumDatabasePermissions($Name) | - Where-Object -FilterScript { - $_.PermissionState -eq $PermissionState - } - - if ($databasePermissionInfo) - { - # Initialize variable permission - [System.String[]] $getSqlDatabasePermissionResult = @() - - foreach ($currentDatabasePermissionInfo in $databasePermissionInfo) - { - $permissionProperty = ($currentDatabasePermissionInfo.PermissionType | - Get-Member -MemberType Property).Name - - foreach ($currentPermissionProperty in $permissionProperty) - { - if ($currentDatabasePermissionInfo.PermissionType."$currentPermissionProperty") - { - $getSqlDatabasePermissionResult += $currentPermissionProperty - } - } - - # Remove any duplicate permissions. - $getSqlDatabasePermissionResult = @( - $getSqlDatabasePermissionResult | - Sort-Object -Unique - ) - } - - if ($getSqlDatabasePermissionResult) - { - $returnValue['Permissions'] = $getSqlDatabasePermissionResult - - $compareObjectParameters = @{ - ReferenceObject = $Permissions - DifferenceObject = $getSqlDatabasePermissionResult - } - - $resultOfPermissionCompare = Compare-Object @compareObjectParameters | - Where-Object -FilterScript { - $_.SideIndicator -eq '<=' - } - - # If there are no missing permission then return 'Ensure' state as 'Present'. - if ($null -eq $resultOfPermissionCompare) - { - $returnValue['Ensure'] = 'Present' - } - } - } - } - } - - return $returnValue -} - -<# - .SYNOPSIS - Sets the permissions for the user in the database. - - .PARAMETER Ensure - This is The Ensure if the permission should be granted (Present) or - revoked (Absent). - - .PARAMETER DatabaseName - This is the SQL database - - .PARAMETER Name - This is the name of the SQL login for the permission set. - - .PARAMETER PermissionState - This is the state of permission set. Valid values are 'Grant' or 'Deny'. - - .PARAMETER Permissions - This is a list that represents a SQL Server set of database permissions. - - .PARAMETER ServerName - This is the SQL Server for the database. Default value is the current - computer name. - - .PARAMETER InstanceName - This is the SQL instance for the database. -#> -function Set-TargetResource -{ - [CmdletBinding()] - param - ( - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure, - - [Parameter(Mandatory = $true)] - [System.String] - $DatabaseName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter(Mandatory = $true)] - [ValidateSet('Grant', 'Deny', 'GrantWithGrant')] - [System.String] - $PermissionState, - - [Parameter(Mandatory = $true)] - [System.String[]] - $Permissions, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ServerName = (Get-ComputerName), - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName = 'MSSQLSERVER' - ) - - $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName - if ($sqlServerObject) - { - Write-Verbose -Message ( - $script:localizedData.ChangePermissionForUser -f $Name, $DatabaseName, $InstanceName - ) - - if ($sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName]) - { - $nameExist = $sqlDatabaseObject.Users[$Name] ` - -or ( - <# - Skip fixed roles like db_datareader as it is not possible to set - permissions on those. - #> - $sqlDatabaseObject.Roles | Where-Object -FilterScript { - -not $_.IsFixedRole -and $_.Name -eq $Name - } - ) ` - -or $sqlDatabaseObject.ApplicationRoles[$Name] - - if ($nameExist) - { - try - { - $permissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' - - foreach ($permission in $permissions) - { - $permissionSet."$permission" = $true - } - - switch ($Ensure) - { - 'Present' - { - Write-Verbose -Message ( - $script:localizedData.AddPermission -f $PermissionState, ($Permissions -join ','), $DatabaseName - ) - - switch ($PermissionState) - { - 'GrantWithGrant' - { - $sqlDatabaseObject.Grant($permissionSet, $Name, $true) - } - - 'Grant' - { - $sqlDatabaseObject.Grant($permissionSet, $Name) - } - - 'Deny' - { - $sqlDatabaseObject.Deny($permissionSet, $Name) - } - } - } - - 'Absent' - { - Write-Verbose -Message ( - $script:localizedData.DropPermission -f $PermissionState, ($Permissions -join ','), $DatabaseName - ) - - if ($PermissionState -eq 'GrantWithGrant') - { - $sqlDatabaseObject.Revoke($permissionSet, $Name, $false, $true) - } - else - { - $sqlDatabaseObject.Revoke($permissionSet, $Name) - } - } - } - } - catch - { - $errorMessage = $script:localizedData.FailedToSetPermissionDatabase -f $Name, $DatabaseName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - } - else - { - $errorMessage = $script:localizedData.NameIsMissing -f $Name, $DatabaseName - - New-InvalidOperationException -Message $errorMessage - } - } - else - { - $errorMessage = $script:localizedData.DatabaseNotFound -f $DatabaseName - New-ObjectNotFoundException -Message $errorMessage - } - } -} - -<# - .SYNOPSIS - Tests if the permissions is set for the user in the database. - - .PARAMETER Ensure - This is The Ensure if the permission should be granted (Present) or - revoked (Absent). - - .PARAMETER DatabaseName - This is the SQL database - - .PARAMETER Name - This is the name of the SQL login for the permission set. - - .PARAMETER PermissionState - This is the state of permission set. Valid values are 'Grant' or 'Deny'. - - .PARAMETER Permissions - This is a list that represents a SQL Server set of database permissions. - - .PARAMETER ServerName - This is the SQL Server for the database. Default value is the current - computer name. - - .PARAMETER InstanceName - This is the SQL instance for the database. -#> -function Test-TargetResource -{ - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Connect-Sql is called when Get-TargetResource is called')] - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure, - - [Parameter(Mandatory = $true)] - [System.String] - $DatabaseName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter(Mandatory = $true)] - [ValidateSet('Grant', 'Deny', 'GrantWithGrant')] - [System.String] - $PermissionState, - - [Parameter(Mandatory = $true)] - [System.String[]] - $Permissions, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ServerName = (Get-ComputerName), - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName = 'MSSQLSERVER' - ) - - Write-Verbose -Message ( - $script:localizedData.TestingConfiguration -f $Name, $DatabaseName, $InstanceName - ) - - $getTargetResourceParameters = @{ - InstanceName = $PSBoundParameters.InstanceName - ServerName = $ServerName - DatabaseName = $PSBoundParameters.DatabaseName - Name = $PSBoundParameters.Name - PermissionState = $PSBoundParameters.PermissionState - Permissions = $PSBoundParameters.Permissions - } - - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - - <# - There is no need to evaluate the parameter Permissions here. - In the Get-TargetResource function there is a test to verify if Permissions is in - desired state. If the permissions are correct, then Get-TargetResource will return - the value 'Present' for the Ensure parameter, otherwise Ensure will have the value - 'Absent'. - #> - return Test-DscParameterState -CurrentValues $getTargetResourceResult ` - -DesiredValues $PSBoundParameters ` - -ValuesToCheck @('Name', 'Ensure', 'PermissionState') ` - -TurnOffTypeChecking -} diff --git a/source/DSCResources/DSC_SqlDatabasePermission/en-US/DSC_SqlDatabasePermission.strings.psd1 b/source/DSCResources/DSC_SqlDatabasePermission/en-US/DSC_SqlDatabasePermission.strings.psd1 deleted file mode 100644 index 014c04d97..000000000 --- a/source/DSCResources/DSC_SqlDatabasePermission/en-US/DSC_SqlDatabasePermission.strings.psd1 +++ /dev/null @@ -1,10 +0,0 @@ -ConvertFrom-StringData @' - GetDatabasePermission = Get permissions for the user '{0}' in the database '{1}' on the instance '{2}'. - DatabaseNotFound = The database '{0}' does not exist. - ChangePermissionForUser = Changing the permission for the user '{0}' in the database '{1}' on the instance '{2}'. - NameIsMissing = The name '{0}' is neither a database user, database role (user-defined), or database application role in the database '{1}'. - AddPermission = {0} the permissions '{1}' to the database '{2}'. - DropPermission = Revoking the {0} permissions '{1}' from the database '{2}'. - FailedToSetPermissionDatabase = Failed to set the permissions for the login '{0}' in the database '{1}'. - TestingConfiguration = Determines if the user '{0}' has the correct permissions in the database '{1}' on the instance '{2}'. -'@ diff --git a/source/Public/Set-SqlDscDatabasePermission.ps1 b/source/Public/Set-SqlDscDatabasePermission.ps1 index 71d09715c..af6cacdd0 100644 --- a/source/Public/Set-SqlDscDatabasePermission.ps1 +++ b/source/Public/Set-SqlDscDatabasePermission.ps1 @@ -12,6 +12,19 @@ Specifies the name of the database principal for which the permissions are returned. + .PARAMETER State + Specifies the state of the permission. + + .PARAMETER Permission + Specifies the permissions. + + .PARAMETER WithGrant + Specifies that the principal should also be granted the right to grant + other principals the same permission. This parameter is only valid when + parameter **State** is set to `Grant` or `Revoke`. When the parameter + **State** is set to `Revoke` the right to grant will also be revoked, + and the revocation will cascade. + .OUTPUTS None. diff --git a/source/en-US/SqlDatabasePermission.strings.psd1 b/source/en-US/SqlDatabasePermission.strings.psd1 index 733d8f7c7..afe1275d9 100644 --- a/source/en-US/SqlDatabasePermission.strings.psd1 +++ b/source/en-US/SqlDatabasePermission.strings.psd1 @@ -10,12 +10,7 @@ ConvertFrom-StringData @' # Strings directly used by the derived class SqlDatabasePermission. EvaluateDatabasePermissionForPrincipal = Evaluate the current permissions for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0001) - - DatabaseNotFound = The database '{0}' does not exist. (SDP0002) + NameIsMissing = The name '{0}' is neither a database user, database role (user-defined), or database application role in the database '{1}', or the database '{1}' does not exist on the instance '{2}'. (SDP0004) ChangePermissionForUser = Changing the permission for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0003) - NameIsMissing = The name '{0}' is neither a database user, database role (user-defined), or database application role in the database '{1}'. (SDP0004) - AddPermission = {0} the permissions '{1}' to the database '{2}'. (SDP0005) - DropPermission = Revoking the {0} permissions '{1}' from the database '{2}'. (SDP0006) - FailedToSetPermissionDatabase = Failed to set the permissions for the login '{0}' in the database '{1}'. (SDP0007) - TestingConfiguration = Determines if the user '{0}' has the correct permissions in the database '{1}' on the instance '{2}'. (SDP0008) + FailedToSetPermissionDatabase = Failed to set the permissions for the user '{0}' in the database '{1}'. (SDP0007) '@ diff --git a/tests/Unit/DSC_SqlDatabasePermission.Tests.ps1 b/tests/Unit/DSC_SqlDatabasePermission.Tests.ps1 deleted file mode 100644 index 39484f940..000000000 --- a/tests/Unit/DSC_SqlDatabasePermission.Tests.ps1 +++ /dev/null @@ -1,1052 +0,0 @@ -<# - .SYNOPSIS - Unit test for DSC_SqlDatabasePermission DSC resource. -#> - -# Suppressing this rule because Script Analyzer does not understand Pester's syntax. -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] -param () - -BeforeDiscovery { - try - { - if (-not (Get-Module -Name 'DscResource.Test')) - { - # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. - if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) - { - # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null - } - - # If the dependencies has not been resolved, this will throw an error. - Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' - } - } - catch [System.IO.FileNotFoundException] - { - throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' - } -} - -BeforeAll { - $script:dscModuleName = 'SqlServerDsc' - $script:dscResourceName = 'DSC_SqlDatabasePermission' - - $script:testEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:dscModuleName ` - -DSCResourceName $script:dscResourceName ` - -ResourceType 'Mof' ` - -TestType 'Unit' - - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') - - # Loading mocked classes - Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') - - # Load the correct SQL Module stub - $script:stubModuleName = Import-SQLModuleStub -PassThru - - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscResourceName - $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscResourceName - $PSDefaultParameterValues['Should:ModuleName'] = $script:dscResourceName -} - -AfterAll { - $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') - $PSDefaultParameterValues.Remove('Mock:ModuleName') - $PSDefaultParameterValues.Remove('Should:ModuleName') - - Restore-TestEnvironment -TestEnvironment $script:testEnvironment - - # Unload the module being tested so that it doesn't impact any other tests. - Get-Module -Name $script:dscResourceName -All | Remove-Module -Force - - # Unload the stub module. - Remove-SqlModuleStub -Name $script:stubModuleName - - # Remove module common test helper. - Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force -} - -Describe 'SqlDatabasePermission\Get-TargetResource' -Tag 'Get' { - BeforeAll { - $mockConnectSQL = { - return @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { - return @{ - 'AdventureWorks' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { - return @{ - 'Zebes\SamusAran' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force - ) - ) - } - } -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { - return @{ - 'MyAppRole' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force - ) - ) - } - } -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { - return @{ - 'public' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'public' | - Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force - ) - ) - } - } -PassThru | - Add-Member -MemberType 'ScriptMethod' -Name 'EnumDatabasePermissions' -Value { - param - ( - [Parameter()] - [System.String] - $SqlServerLogin - ) - if ($mockInvalidOperationEnumDatabasePermissions) - { - throw 'Mock EnumDatabasePermissions Method was called with invalid operation.' - } - - if ( $SqlServerLogin -eq 'Zebes\SamusAran' ) - { - $mockEnumDatabasePermissions = @() - $mockEnumDatabasePermissions += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'Zebes\SamusAran' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - $mockEnumDatabasePermissions += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'Zebes\SamusAran' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockEnumDatabasePermissions - } - else - { - return $null - } - } -PassThru -Force - ) - ) - } - } -PassThru -Force - ) - ) - } - - Mock -CommandName Connect-SQL -MockWith $mockConnectSQL - - InModuleScope -ScriptBlock { - # Default parameters that are used for the It-blocks. - $script:mockDefaultParameters = @{ - InstanceName = 'MSSQLSERVER' - ServerName = 'localhost' - } - } - } - - BeforeEach { - InModuleScope -ScriptBlock { - $script:mockGetTargetResourceParameters = $script:mockDefaultParameters.Clone() - } - } - - Context 'When the system is in the desired state' { - Context 'When the desired permission does exist' { - BeforeEach { - InModuleScope -ScriptBlock { - $mockGetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockGetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockGetTargetResourceParameters.PermissionState = 'Grant' - $mockGetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - } - } - - It 'Should return the state as present' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Ensure | Should -Be 'Present' - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - It 'Should return the same values as passed as parameters' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.ServerName | Should -Be $mockGetTargetResourceParameters.ServerName - $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName - $result.ServerRoleName | Should -Be $mockGetTargetResourceParameters.ServerRoleName - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When the desired permission does not exist' { - BeforeEach { - InModuleScope -ScriptBlock { - $mockGetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockGetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockGetTargetResourceParameters.PermissionState = 'Grant' - $mockGetTargetResourceParameters.Permissions = @( 'Connect', 'Update', 'Select' ) - } - } - - It 'Should not return the state as absent' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Ensure | Should -Not -Be 'Present' - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should return the same values as passed as parameters' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.ServerName | Should -Be $mockGetTargetResourceParameters.ServerName - $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName - $result.ServerRoleName | Should -Be $mockGetTargetResourceParameters.ServerRoleName - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - } - - Context 'When the system is not in the desired state' { - Context 'When passing values to parameters and database does not exist' { - It 'Should throw the correct error' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockGetTargetResourceParameters.DatabaseName = 'unknownDatabaseName' - $mockGetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockGetTargetResourceParameters.PermissionState = 'Grant' - $mockGetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Ensure | Should -Be 'Absent' - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When permissions are missing' { - BeforeEach { - InModuleScope -ScriptBlock { - $mockGetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockGetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockGetTargetResourceParameters.PermissionState = 'Grant' - $mockGetTargetResourceParameters.Permissions = @( 'Connect', 'Update', 'Select' ) - } - } - - It 'Should return the state as absent' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Ensure | Should -Be 'Absent' - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should return the same values as passed as parameters' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.ServerName | Should -Be $mockGetTargetResourceParameters.ServerName - $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName - $result.ServerRoleName | Should -Be $mockGetTargetResourceParameters.ServerRoleName - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When the desired permission does not exist' { - BeforeEach { - InModuleScope -ScriptBlock { - $mockGetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockGetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockGetTargetResourceParameters.PermissionState = 'Grant' - $mockGetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - } - } - - It 'Should not return the state as absent' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Ensure | Should -Not -Be 'Absent' - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should return the same values as passed as parameters' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.ServerName | Should -Be $mockGetTargetResourceParameters.ServerName - $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName - $result.ServerRoleName | Should -Be $mockGetTargetResourceParameters.ServerRoleName - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - } -} - -Describe "SqlDatabasePermission\Test-TargetResource" -Tag 'Test' { - BeforeAll { - InModuleScope -ScriptBlock { - # Default parameters that are used for the It-blocks. - $script:mockDefaultParameters = @{ - InstanceName = 'MSSQLSERVER' - ServerName = 'localhost' - } - } - } - - BeforeEach { - InModuleScope -ScriptBlock { - $script:mockTestTargetResourceParameters = $script:mockDefaultParameters.Clone() - } - } - - Context 'When the system is in the desired state' { - Context 'When the desired permission should exist' { - BeforeAll { - Mock -CommandName Get-TargetResource -MockWith { - return @{ - ServerName = 'localhost' - InstanceName = 'MSSQLSERVER' - Name = 'Zebes\SamusAran' - PermissionState = 'Grant' - Permissions = @( 'Connect', 'Update' ) - Ensure = 'Present' - } - } - } - - It 'Should return $true' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockTestTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockTestTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockTestTargetResourceParameters.PermissionState = 'Grant' - $mockTestTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockTestTargetResourceParameters.Ensure = 'Present' - - $result = Test-TargetResource @mockTestTargetResourceParameters -Verbose - - $result | Should -BeTrue - } - - Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It - } - } - - Context 'When the desired permission should not exist' { - BeforeAll { - Mock -CommandName Get-TargetResource -MockWith { - return @{ - ServerName = 'localhost' - InstanceName = 'MSSQLSERVER' - Name = 'Zebes\SamusAran' - PermissionState = 'Grant' - Permissions = @() - Ensure = 'Absent' - } - } - } - - It 'Should return $true' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockTestTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockTestTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockTestTargetResourceParameters.PermissionState = 'Grant' - $mockTestTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockTestTargetResourceParameters.Ensure = 'Absent' - - Test-TargetResource @mockTestTargetResourceParameters | Should -BeTrue - } - - Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It - } - } - } - - Context 'When the system is not in the desired state' { - Context 'When the desired permission are missing' { - BeforeAll { - Mock -CommandName Get-TargetResource -MockWith { - return @{ - ServerName = 'localhost' - InstanceName = 'MSSQLSERVER' - Name = 'Zebes\SamusAran' - PermissionState = 'Grant' - Permissions = @( 'Connect', 'Update' ) - Ensure = 'Absent' - } - } - } - - It 'Should return the state as true ' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockTestTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockTestTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockTestTargetResourceParameters.PermissionState = 'Grant' - $mockTestTargetResourceParameters.Permissions = @( 'Connect', 'Update', 'Select' ) - $mockTestTargetResourceParameters.Ensure = 'Present' - - Test-TargetResource @mockTestTargetResourceParameters | Should -BeFalse - } - - Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It - } - } - - Context 'When there are more permissions than desired' { - BeforeAll { - Mock -CommandName Get-TargetResource -MockWith { - return @{ - ServerName = 'localhost' - InstanceName = 'MSSQLSERVER' - Name = 'Zebes\SamusAran' - PermissionState = 'Grant' - Permissions = @( 'Connect', 'Update' ) - Ensure = 'Absent' - } - } - } - - It 'Should return the state as true ' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockTestTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockTestTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockTestTargetResourceParameters.PermissionState = 'Grant' - $mockTestTargetResourceParameters.Permissions = @( 'Connect' ) - $mockTestTargetResourceParameters.Ensure = 'Present' - - Test-TargetResource @mockTestTargetResourceParameters | Should -BeFalse - } - - Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It - } - } - } -} - -Describe "DSC_SqlDatabasePermission\Set-TargetResource" -Tag 'Set' { - BeforeAll { - $mockConnectSQL = { - return @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { - return @{ - 'AdventureWorks' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { - return @{ - 'Zebes\SamusAran' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force - ) - ) - } - } -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { - return @{ - 'MyAppRole' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force - ) - ) - } - } -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { - return @{ - 'public' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'public' | - Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force - ) - ) - } - } -PassThru | - Add-Member -MemberType 'ScriptMethod' -Name 'Grant' -Value { - param - ( - [Parameter()] - [System.Object] - $permissionSet, - - [Parameter()] - [System.String] - $SqlServerLogin - ) - - if ($mockInvalidOperationForGrantMethod) - { - throw 'Mocked InvalidOperationException' - } - - InModuleScope -ScriptBlock { - $script:mockMethodGrantWasRan += 1 - } - - # Make sure the method is called with the expected login - if ( $SqlServerLogin -ne 'Zebes\SamusAran' ) - { - throw "Called mocked Grant() method without setting the right login name. Expected '{0}'. But was '{1}'." ` - -f 'Zebes\SamusAran', $SqlServerLogin - } - } -PassThru | - Add-Member -MemberType 'ScriptMethod' -Name 'Revoke' -Value { - param - ( - [Parameter()] - [System.Object] - $permissionSet, - - [Parameter()] - [System.String] - $SqlServerLogin - ) - - if ($mockInvalidOperationForRevokeMethod) - { - throw 'Mocked InvalidOperationException' - } - - InModuleScope -ScriptBlock { - $script:mockMethodRevokeWasRan += 1 - } - - # Make sure the method is called with the expected login - if ( $SqlServerLogin -ne 'Zebes\SamusAran' ) - { - throw "Called mocked Revoke() method without setting the right login name. Expected '{0}'. But was '{1}'." ` - -f 'Zebes\SamusAran', $SqlServerLogin - } - } -PassThru | - Add-Member -MemberType 'ScriptMethod' -Name 'Deny' -Value { - param - ( - [Parameter()] - [System.Object] - $permissionSet, - - [Parameter()] - [System.String] - $SqlServerLogin - ) - - if ($mockInvalidOperationForDenyMethod) - { - throw 'Mocked InvalidOperationException' - } - - InModuleScope -ScriptBlock { - $script:mockMethodDenyWasRan += 1 - } - - # Make sure the method is called with the expected login - if ( $SqlServerLogin -ne 'Zebes\SamusAran' ) - { - throw "Called mocked Deny() method without setting the right login name. Expected '{0}'. But was '{1}'." ` - -f 'Zebes\SamusAran', $SqlServerLogin - } - } -PassThru -Force - ) - ) - } - } -PassThru -Force - ) - ) - } - - Mock -CommandName Connect-SQL -MockWith $mockConnectSQL - - InModuleScope -ScriptBlock { - # Default parameters that are used for the It-blocks. - $script:mockDefaultParameters = @{ - InstanceName = 'MSSQLSERVER' - ServerName = 'localhost' - } - } - } - - BeforeEach { - InModuleScope -ScriptBlock { - $script:mockSetTargetResourceParameters = $script:mockDefaultParameters.Clone() - - $script:mockMethodGrantWasRan = 0 - $script:mockMethodDenyWasRan = 0 - $script:mockMethodRevokeWasRan = 0 - $script:mockMethodCreateWasRan = 0 - } - } - - Context 'When passing values to parameters and database name does not exist' { - It 'Should throw the correct error' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'unknownDatabaseName' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'Grant' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Present' - - $errorMessage = $script:localizedData.DatabaseNotFound -f $mockSetTargetResourceParameters.DatabaseName - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw ('*' + $errorMessage) - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When passing values to parameters and database user does not exist' { - It 'Should throw the correct error' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'unknownLoginNamen' - $mockSetTargetResourceParameters.PermissionState = 'Grant' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Present' - - $errorMessage = $script:localizedData.NameIsMissing -f $mockSetTargetResourceParameters.Name, 'AdventureWorks' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw ('*' + $errorMessage) - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When the system is not in the desired state' { - Context 'When Ensure is set to Present' { - It 'Should call the method Grant() without throwing' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'Grant' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Present' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw - - $script:mockMethodGrantWasRan | Should -Be 1 - $script:mockMethodDenyWasRan | Should -Be 0 - $script:mockMethodRevokeWasRan | Should -Be 0 - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should call the method Grant() (WithGrant) without throwing' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'GrantWithGrant' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Present' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw - - $script:mockMethodGrantWasRan | Should -Be 1 - $script:mockMethodDenyWasRan | Should -Be 0 - $script:mockMethodRevokeWasRan | Should -Be 0 - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should call the method Deny() without throwing' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'Deny' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Present' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw - - $script:mockMethodGrantWasRan | Should -Be 0 - $script:mockMethodDenyWasRan | Should -Be 1 - $script:mockMethodRevokeWasRan | Should -Be 0 - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When Ensure is set to Absent' { - It 'Should call the method Revoke() for permission state ''Grant'' without throwing' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'Grant' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Absent' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw - - $script:mockMethodGrantWasRan | Should -Be 0 - $script:mockMethodDenyWasRan | Should -Be 0 - $script:mockMethodRevokeWasRan | Should -Be 1 - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should call the method Revoke() for permission state ''GrantWithGrant'' without throwing' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'GrantWithGrant' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Absent' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw - - $script:mockMethodGrantWasRan | Should -Be 0 - $script:mockMethodDenyWasRan | Should -Be 0 - $script:mockMethodRevokeWasRan | Should -Be 1 - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should call the method Revoke() for permission state ''Deny'' without throwing' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'Deny' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Absent' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw - - $script:mockMethodGrantWasRan | Should -Be 0 - $script:mockMethodDenyWasRan | Should -Be 0 - $script:mockMethodRevokeWasRan | Should -Be 1 - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When method fails' -ForEach @( - @{ - MockMethod = 'Grant' - MockVariableName = 'mockInvalidOperationForGrantMethod' - MockEnsure = 'Present' - } - @{ - MockMethod = 'Grant' - MockVariableName = 'mockInvalidOperationForRevokeMethod' - MockEnsure = 'Absent' - } - @{ - MockMethod = 'Deny' - MockVariableName = 'mockInvalidOperationForDenyMethod' - MockEnsure = 'Present' - } - ) { - BeforeAll { - Set-Variable -Name $MockVariableName -Value $true - } - - AfterAll { - Set-Variable -Name $MockVariableName -Value $false - } - - It 'Should throw a mocked invalid operation error' { - InModuleScope -Parameters $_ -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = $MockMethod - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = $MockEnsure - - $errorMessage = $script:localizedData.FailedToSetPermissionDatabas -f 'Zebes\SamusAran', 'AdventureWorks' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw -ExpectedMessage ('*' + $errorMessage + '*') - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - } -} - -# try -# { -# InModuleScope $script:dscResourceName { -# $mockServerName = 'localhost' -# $mockInstanceName = 'MSSQLSERVER' -# 'AdventureWorks' = 'AdventureWorks' -# 'Zebes\SamusAran' = 'Zebes\SamusAran' -# 'public' = 'public' -# 'MyAppRole' = 'MyAppRole' -# 'Zebes\SamusAran'Unknown = 'Elysia\Chozo' -# 'WindowsUser' = 'WindowsUser' -# $mockInvalidOperationEnumDatabasePermissions = $false -# $mockInvalidOperationForCreateMethod = $false -# $mockExpectedSqlServerLogin = 'Zebes\SamusAran' -# $mockSqlPermissionState = 'Grant' - -# $mockSqlPermissionType01 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false) -# $mockSqlPermissionType02 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true) - -# $script:mockMethodGrantWasRan = $false -# $script:mockMethodDenyWasRan = $false -# $script:mockMethodRevokeWasRan = $false -# $script:mockMethodCreateWasRan = $false - -# # Default parameters that are used for the It-blocks -# $mockDefaultParameters = @{ -# InstanceName = $mockInstanceName -# ServerName = $mockServerName -# } - -# #region Function mocks -# $mockConnectSQL = { -# return @( -# ( -# New-Object -TypeName Object | -# Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { -# return @{ -# 'AdventureWorks' = @( -# ( -# New-Object -TypeName Object | -# Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | -# Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { -# return @{ -# 'Zebes\SamusAran' = @( -# ( -# New-Object -TypeName Object | -# Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force -# ) -# ) -# } -# } -PassThru | -# Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { -# return @{ -# 'MyAppRole' = @( -# ( -# New-Object -TypeName Object | -# Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force -# ) -# ) -# } -# } -PassThru | -# Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { -# return @{ -# 'public' = @( -# ( -# New-Object -TypeName Object | -# Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'public' | -# Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force -# ) -# ) -# } -# } -PassThru | -# Add-Member -MemberType 'ScriptMethod' -Name 'EnumDatabasePermissions' -Value { -# param -# ( -# [Parameter()] -# [System.String] -# $SqlServerLogin -# ) -# if ($mockInvalidOperationEnumDatabasePermissions) -# { -# throw 'Mock EnumDatabasePermissions Method was called with invalid operation.' -# } - -# if ( $SqlServerLogin -eq $mockExpectedSqlServerLogin ) -# { -# $mockEnumDatabasePermissions = @() -# $mockEnumDatabasePermissions += New-Object -TypeName Object | -# Add-Member -MemberType NoteProperty -Name PermissionType -Value $mockSqlPermissionType01 -PassThru | -# Add-Member -MemberType NoteProperty -Name PermissionState -Value $mockSqlPermissionState -PassThru | -# Add-Member -MemberType NoteProperty -Name Grantee -Value $mockExpectedSqlServerLogin -PassThru | -# Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | -# Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | -# Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru -# $mockEnumDatabasePermissions += New-Object -TypeName Object | -# Add-Member -MemberType NoteProperty -Name PermissionType -Value $mockSqlPermissionType02 -PassThru | -# Add-Member -MemberType NoteProperty -Name PermissionState -Value $mockSqlPermissionState -PassThru | -# Add-Member -MemberType NoteProperty -Name Grantee -Value $mockExpectedSqlServerLogin -PassThru | -# Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | -# Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | -# Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - -# $mockEnumDatabasePermissions -# } -# else -# { -# return $null -# } -# } -PassThru | -# Add-Member -MemberType 'ScriptMethod' -Name 'Grant' -Value { -# param -# ( -# [Parameter()] -# [System.Object] -# $permissionSet, - -# [Parameter()] -# [System.String] -# $SqlServerLogin -# ) - -# $script:mockMethodGrantWasRan = $true - -# if ( $SqlServerLogin -ne $mockExpectedSqlServerLogin ) -# { -# throw "Called mocked Grant() method without setting the right login name. Expected '{0}'. But was '{1}'." ` -# -f $mockExpectedSqlServerLogin, $SqlServerLogin -# } -# } -PassThru | -# Add-Member -MemberType 'ScriptMethod' -Name 'Revoke' -Value { -# param -# ( -# [Parameter()] -# [System.Object] -# $permissionSet, - -# [Parameter()] -# [System.String] -# $SqlServerLogin -# ) - -# $script:mockMethodRevokeWasRan = $true - -# if ( $SqlServerLogin -ne $mockExpectedSqlServerLogin ) -# { -# throw "Called mocked Revoke() method without setting the right login name. Expected '{0}'. But was '{1}'." ` -# -f $mockExpectedSqlServerLogin, $SqlServerLogin -# } -# } -PassThru | -# Add-Member -MemberType 'ScriptMethod' -Name 'Deny' -Value { -# param -# ( -# [Parameter()] -# [System.Object] -# $permissionSet, - -# [Parameter()] -# [System.String] -# $SqlServerLogin -# ) - -# $script:mockMethodDenyWasRan = $true - -# if ( $SqlServerLogin -ne $mockExpectedSqlServerLogin ) -# { -# throw "Called mocked Deny() method without setting the right login name. Expected '{0}'. But was '{1}'." ` -# -f $mockExpectedSqlServerLogin, $SqlServerLogin -# } -# } -PassThru -Force -# ) -# ) -# } -# } -PassThru -Force -# ) -# ) -# } -# #endregion - -# - - - -# } -# } -# finally -# { -# Invoke-TestCleanup -# } From cd09bef7140fc2d59f026115a7712e3d5a6c2850 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 14 Jul 2022 15:46:51 +0200 Subject: [PATCH 43/98] Fix code --- source/Classes/010.ResourceBase.ps1 | 100 ++++++++++-------- source/Classes/020.SqlDatabasePermission.ps1 | 5 +- ...perty.ps1 => Test-ResourceHasProperty.ps1} | 22 ++-- .../Public/Set-SqlDscDatabasePermission.ps1 | 2 +- source/en-US/SqlServerDsc.strings.psd1 | 2 +- ...ps1 => Test-ResourceHasProperty.Tests.ps1} | 49 ++++++++- 6 files changed, 124 insertions(+), 56 deletions(-) rename source/Private/{Test-ResourceHasEnsureProperty.ps1 => Test-ResourceHasProperty.ps1} (53%) rename tests/Unit/Private/{Test-ResourceHasEnsureProperty.Tests.ps1 => Test-ResourceHasProperty.Tests.ps1} (72%) diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index 7d9e881ab..ac036b981 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -6,7 +6,9 @@ A class with methods that are equal for all class-based resources. .NOTES - This class should not contain any DSC properties. + This class should be able to be inherited by all DSC resources. This class + shall not contain any DSC properties, neither shall it contain anything + specific to only a single resource. #> class ResourceBase @@ -84,13 +86,16 @@ class ResourceBase $ignoreProperty = @() - if (($this | Test-ResourceHasEnsureProperty) -and $null -eq $getCurrentStateResult.Ensure) + <# + If the derived DSC resource has a Ensure property and it was not returned + by GetCurrentState(), then the property Ensure is removed from the + comparison (when calling Compare()). The property Ensure is ignored + since the method GetCurrentState() did not return it, and the current + state for property Ensure cannot be determined until the method Compare() + has run to determined if other properties are not in desired state. + #> + if (($this | Test-ResourceHasProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) { - <# - Removing the property Ensure from the comparison since the method - GetCurrentState() did not return it, and we don't know the current - state value until the method Compare() has run. - #> $ignoreProperty += 'Ensure' } @@ -101,10 +106,10 @@ class ResourceBase $propertiesNotInDesiredState = $this.Compare($getCurrentStateResult, $ignoreProperty) <# - Return the correct value for Ensure property if it hasn't been already - set by GetCurrentState(). + Return the correct values for Ensure property if the derived DSC resource + has such property and it hasn't been already set by GetCurrentState(). #> - if (($this | Test-ResourceHasEnsureProperty) -and $null -eq $getCurrentStateResult.Ensure) + if (($this | Test-ResourceHasProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) { if (($propertiesNotInDesiredState -and $this.Ensure -eq [Ensure]::Present) -or (-not $propertiesNotInDesiredState -and $this.Ensure -eq [Ensure]::Absent)) { @@ -120,43 +125,52 @@ class ResourceBase } } - # TODO: And only if $getCurrentStateResult no already contain key Reasons - if ($propertiesNotInDesiredState) + <# + Return the correct values for Reasons property if the derived DSC resource + has such property and it hasn't been already set by GetCurrentState(). + #> + if (($this | Test-ResourceHasProperty -Name 'Reasons') -and -not $getCurrentStateResult.ContainsKey('Reasons')) { - foreach ($property in $propertiesNotInDesiredState) - { - if ($property.ExpectedValue -is [System.Enum]) - { - # TODO: Maybe we just convert the advanced types to JSON and do not convert other types? - # Test that on SqlDatabasePermission - - # Return the string representation of the value so that conversion to json is correct. - $propertyExpectedValue = $property.ExpectedValue.ToString() - } - else - { - $propertyExpectedValue = $property.ExpectedValue - } - - if ($property.ActualValue -is [System.Enum]) - { - # TODO: Maybe we just convert the advanced types to JSON and do not convert other types? - # Test that on SqlDatabasePermission + # Always return an empty array if all properties are in desired state. + $dscResourceObject.Reasons = [Reason[]] @() - # Return the string representation of the value so that conversion to json is correct. - $propertyActualValue = $property.ActualValue.ToString() - } - else + if ($propertiesNotInDesiredState) + { + foreach ($property in $propertiesNotInDesiredState) { - $propertyActualValue = $property.ActualValue + if ($property.ExpectedValue -is [System.Enum]) + { + # TODO: Maybe we just convert the advanced types to JSON and do not convert other types? + # Test that on SqlDatabasePermission + + # Return the string representation of the value (instead of the numeric value). + $propertyExpectedValue = $property.ExpectedValue.ToString() + } + else + { + $propertyExpectedValue = $property.ExpectedValue + } + + if ($property.ActualValue -is [System.Enum]) + { + # TODO: Maybe we just convert the advanced types to JSON and do not convert other types? + # Test that on SqlDatabasePermission + + # Return the string representation of the value so that conversion to json is correct. + $propertyActualValue = $property.ActualValue.ToString() + } + else + { + $propertyActualValue = $property.ActualValue + } + + $dscResourceObject.Reasons += [Reason] @{ + Code = '{0}:{0}:{1}' -f $this.GetType(), $property.Property + Phrase = 'The property {0} should be {1}, but was {2}' -f $property.Property, ($propertyExpectedValue | ConvertTo-Json -Compress), ($propertyActualValue | ConvertTo-Json -Compress) + } + + Write-Verbose -Verbose -Message ($dscResourceObject.Reasons | Out-String) } - - $dscResourceObject.Reasons += [Reason] @{ - Code = '{0}:{0}:{1}' -f $this.GetType(), $property.Property - Phrase = 'The property {0} should be {1}, but was {2}' -f $property.Property, ($propertyExpectedValue | ConvertTo-Json -Compress), ($propertyActualValue | ConvertTo-Json -Compress) - } - - Write-Verbose -Verbose -Message ($dscResourceObject.Reasons | Out-String) } } diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index dda63ee09..e6085833b 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -323,6 +323,7 @@ class SqlDatabasePermission : ResourceBase { if ($properties.ContainsKey('Permission')) { + # TODO: Remove this? # Write-Verbose -Message ( # $this.localizedData.ChangePermissionForUser -f @( # $this.Name, @@ -407,7 +408,9 @@ class SqlDatabasePermission : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { - # TODO: Add the evaluation so that one permission can't have two different states. + # TODO: Add the evaluation so that one permission can't be added two different states ('Grant' and 'Deny') in the same resource instance. + + # TODO: Add the evaluation so that the same State cannot exist several times in the same resource instance. Write-Verbose -Verbose -Message 'NotImplemented: AssertProperties()' # @( diff --git a/source/Private/Test-ResourceHasEnsureProperty.ps1 b/source/Private/Test-ResourceHasProperty.ps1 similarity index 53% rename from source/Private/Test-ResourceHasEnsureProperty.ps1 rename to source/Private/Test-ResourceHasProperty.ps1 index 2a81dbd9c..253fc45cc 100644 --- a/source/Private/Test-ResourceHasEnsureProperty.ps1 +++ b/source/Private/Test-ResourceHasProperty.ps1 @@ -1,17 +1,21 @@ <# .SYNOPSIS - Tests wether the class-based resource has an Ensure property. + Tests wether the class-based resource has the specified property. .DESCRIPTION - Tests wether the class-based resource has an Ensure property. + Tests wether the class-based resource has the specified property. .PARAMETER InputObject - The object that should be tested for existens of property Ensure. + Specifies the object that should be tested for existens of the specified + property. + + .PARAMETER Name + Specifies the name of the property. .OUTPUTS [Boolean] #> -function Test-ResourceHasEnsureProperty +function Test-ResourceHasProperty { [CmdletBinding()] [OutputType([System.Boolean])] @@ -19,14 +23,18 @@ function Test-ResourceHasEnsureProperty ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [PSObject] - $InputObject + $InputObject, + + [Parameter(Mandatory = $true)] + [System.String] + $Name ) $hasEnsure = $false - # Get all key properties. + # Check to see if the property exist and that is it a DSC property. $ensureProperty = $InputObject | - Get-Member -MemberType 'Property' -Name 'Ensure' | + Get-Member -MemberType 'Property' -Name $Name | Where-Object -FilterScript { $InputObject.GetType().GetMember($_.Name).CustomAttributes.Where( { diff --git a/source/Public/Set-SqlDscDatabasePermission.ps1 b/source/Public/Set-SqlDscDatabasePermission.ps1 index af6cacdd0..c2b8a4298 100644 --- a/source/Public/Set-SqlDscDatabasePermission.ps1 +++ b/source/Public/Set-SqlDscDatabasePermission.ps1 @@ -113,7 +113,7 @@ function Set-SqlDscDatabasePermission { # TODO: Set permissions. Write-Verbose -Message ( - $script:localizedData.DatabasePermission_ChangePermissionForUser -f $Name, $DatabaseName, $InstanceName + $script:localizedData.DatabasePermission_ChangePermissionForUser -f $Name, $DatabaseName, $ServerObject.InstanceName ) # Get the permissions names that are set to $true in the DatabasePermissionSet. diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 23ac49f31..86dd7e70c 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -11,7 +11,7 @@ ConvertFrom-StringData @' DatabasePermission_MissingDatabase = The database '{0}' cannot be found. # Set-SqlDscDatabasePermission - DatabasePermission_ChangePermissionForUser = Changing the permission for the user '{0}' in the database '{1}' on the instance '{2}'. + DatabasePermission_ChangePermissionForUser = Changing the permission for the principal '{0}' in the database '{1}' on the instance '{2}'. DatabasePermission_GrantPermission = Grant the permissions '{0}' for the principal '{1}'. DatabasePermission_DenyPermission = Deny the permissions '{0}' for the principal '{1}'. DatabasePermission_RevokePermission = Revoke the permissions '{0}' for the principal '{1}'. diff --git a/tests/Unit/Private/Test-ResourceHasEnsureProperty.Tests.ps1 b/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 similarity index 72% rename from tests/Unit/Private/Test-ResourceHasEnsureProperty.Tests.ps1 rename to tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 index 90bd4b970..484ee79b5 100644 --- a/tests/Unit/Private/Test-ResourceHasEnsureProperty.Tests.ps1 +++ b/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 @@ -42,7 +42,7 @@ AfterAll { Get-Module -Name $script:dscModuleName -All | Remove-Module -Force } -Describe 'Test-ResourceHasEnsureProperty' -Tag 'Private' { +Describe 'Test-ResourceHasProperty' -Tag 'Private' { Context 'When resource does not have an Ensure property' { BeforeAll { <# @@ -80,7 +80,7 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() It 'Should return the correct value' { InModuleScope -ScriptBlock { - $result = Test-ResourceHasEnsureProperty -InputObject $script:mockResourceBaseInstance + $result = Test-ResourceHasProperty -Name 'Ensure' -InputObject $script:mockResourceBaseInstance $result | Should -BeFalse } @@ -124,10 +124,53 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() It 'Should return the correct value' { InModuleScope -ScriptBlock { - $result = Test-ResourceHasEnsureProperty -InputObject $script:mockResourceBaseInstance + $result = Test-ResourceHasProperty -Name 'Ensure' -InputObject $script:mockResourceBaseInstance $result | Should -BeTrue } } } + + Context 'When resource have an Ensure property, but it is not a DSC property' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [System.String] + $Ensure + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceHasProperty -Name 'Ensure' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeFalse + } + } + } } From 7046270925bd236383a633f4a876fb9f5e620958 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 14 Jul 2022 16:32:54 +0200 Subject: [PATCH 44/98] Fix code --- source/Classes/020.SqlDatabasePermission.ps1 | 25 +++++++++++++++++++ .../Public/Set-SqlDscDatabasePermission.ps1 | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index e6085833b..c0626f1a0 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -284,6 +284,31 @@ class SqlDatabasePermission : ResourceBase } } + <# + TODO: + Need to handle Absent state: The property Permission is + in desired state because the desired state permission + ('update') does not exist in current state: + {"State":"Grant","Permission":["update"]}, but was {"State":"Grant","Permission":["Connect"]} + + When $this.Ensure is 'Absent' and the node is in desired + state the current state permissions will always differ + from desired state permissions, since the current permission + will never have the permission that desired state says + should be removed. + + When $this.Ensure is 'Absent', and the permission does + not exist in the current state when we should add 'Permission' + to the property $this.notEnforcedProperties so that the + Permission property is not compared. This way we return + the correct current state. Maybe we can set + $this.notEnforcedProperties in the constructor? + + We should also return the property 'Ensure' set to 'Absent' + so the base class does not try to evaluate the state itself. + The base class does not know and cannot know how to evaluate + the permissions correctly. + #> return $currentState } diff --git a/source/Public/Set-SqlDscDatabasePermission.ps1 b/source/Public/Set-SqlDscDatabasePermission.ps1 index c2b8a4298..dd8132a8a 100644 --- a/source/Public/Set-SqlDscDatabasePermission.ps1 +++ b/source/Public/Set-SqlDscDatabasePermission.ps1 @@ -121,7 +121,7 @@ function Set-SqlDscDatabasePermission Get-Member -MemberType 'Property' | Select-Object -ExpandProperty 'Name' | Where-Object -FilterScript { - $setPermission.$_ + $Permission.$_ } switch ($State) From 541415bfdf7cc5fb3929ca4a2378a6506bdc773e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 17 Jul 2022 09:58:49 +0200 Subject: [PATCH 45/98] Fix code --- source/Classes/020.SqlDatabasePermission.ps1 | 143 +++++++++++++----- .../en-US/SqlDatabasePermission.strings.psd1 | 2 +- .../Classes/SqlDatabasePermission.Tests.ps1 | 139 ++++++++++++++++- 3 files changed, 244 insertions(+), 40 deletions(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index c0626f1a0..4baf41fee 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -39,7 +39,7 @@ ```plaintext Failed to create an object of PowerShell class SqlDatabasePermission. - + CategoryInfo : InvalidOperation: (root/Microsoft/...gurationManager:String) [], CimException + + CategoryInfo : InvalidOperation: (root/Microsoft/...ConfigurationManager:String) [], CimException + FullyQualifiedErrorId : InstantiatePSClassObjectFailed + PSComputerName : localhost ``` @@ -85,7 +85,7 @@ InstanceName = 'SQL2017' DatabaseName = 'AdventureWorks' Credential = $SqlInstallCredential - Name = 'SQLTEST\sqluser' + Name = 'INSTANCE\SqlUser' Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( ( New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ @@ -230,6 +230,7 @@ class SqlDatabasePermission : ResourceBase $databasePermissionInfo = $sqlServerObject | Get-SqlDscDatabasePermission -DatabaseName $this.DatabaseName -Name $this.Name -ErrorAction 'SilentlyContinue' + # If permissions was returned, build the current permission array of [DatabasePermission]. if ($databasePermissionInfo) { $permissionState = @( @@ -285,37 +286,98 @@ class SqlDatabasePermission : ResourceBase } <# - TODO: - Need to handle Absent state: The property Permission is - in desired state because the desired state permission - ('update') does not exist in current state: - {"State":"Grant","Permission":["update"]}, but was {"State":"Grant","Permission":["Connect"]} - When $this.Ensure is 'Absent' and the node is in desired state the current state permissions will always differ from desired state permissions, since the current permission - will never have the permission that desired state says + should never have the permission that desired state says should be removed. - When $this.Ensure is 'Absent', and the permission does - not exist in the current state when we should add 'Permission' - to the property $this.notEnforcedProperties so that the - Permission property is not compared. This way we return - the correct current state. Maybe we can set - $this.notEnforcedProperties in the constructor? - - We should also return the property 'Ensure' set to 'Absent' - so the base class does not try to evaluate the state itself. The base class does not know and cannot know how to evaluate - the permissions correctly. + absent permissions correctly. So this needs to evaluated here. #> + if ($this.Ensure -eq [Ensure]::Absent) + { + $inDesiredState = $true + + # Evaluate so that the desired state is missing from the current state. + foreach ($desiredPermission in $this.Permission) + { + $currentStatePermissionForState = $currentState.Permission | + Where-Object -FilterScript { + $_.State -eq $desiredPermission.State + } + + foreach ($desiredPermissionName in $desiredPermission.Permission) + { + if ($currentStatePermissionForState.Permission -contains $desiredPermissionName) + { + Write-Verbose -Message ( + $this.localizedData.DesiredAbsentPermissionArePresent -f @( + $this.Name, + $this.DatabaseName, + $this.InstanceName + ) + ) + + $inDesiredState = $false + } + } + } + + <# + When $this.Ensure is 'Absent' then 'Permission' must be added to + the property $this.notEnforcedProperties so that the Permission + property is not compared. This is especially true when the + permissions are in desired state. But when the permissions are not + in desired state, if the Permission property would be 0compared by + the base class, it would not be intuitive to the user + since the verbose messages output from the base class will say + that it expects the permissions to be that of what should be absent. + #> + $this.notEnforcedProperties += 'Permission' + + <# + We should also return the property 'Ensure' set to 'Absent' or + 'Present' so the base class does not try to evaluate the state + itself. + #> + if ($inDesiredState) + { + <# + The desired permission that should be absent does not exist + in the current state, therefor we return 'Absent'. + #> + $currentState.Ensure = [Ensure]::Absent + } + else + { + <# + The desired permission that should be absent exist in the current + state, therefor we return 'Present'. + #> + $currentState.Ensure = [Ensure]::Present + } + } + else + { + <# + If the property notEnforcedProperties contains 'Permission', remove it. + This is a fail-safe if the same class instance would be re-used. + #> + if ($this.notEnforcedProperties -contains 'Permission') + { + $this.notEnforcedProperties = @($this.notEnforcedProperties) -ne 'Permission' + } + } + return $currentState } <# Base method Set() call this method with the properties that should be enforced and that are not in desired state. It is not called if all - properties are in desired state. + properties are in desired state. The variable $properties contain the + properties that are not in desired state. #> hidden [void] Modify([System.Collections.Hashtable] $properties) { @@ -346,18 +408,26 @@ class SqlDatabasePermission : ResourceBase if ($isDatabasePrincipal) { - if ($properties.ContainsKey('Permission')) + <# + Update permissions if: + + - $properties contains property Permission + - $properties contains property Ensure and it is set to 'Absent' + + First will happen when there are additional permissions to add + to the current state. + + Second will happen when there are permissions in the current state + that should be absent. + #> + if ($properties.ContainsKey('Permission') -or + ( + $properties.ContainsKey('Ensure') -and + $properties.Ensure -eq [Ensure]::Absent + ) + ) { - # TODO: Remove this? - # Write-Verbose -Message ( - # $this.localizedData.ChangePermissionForUser -f @( - # $this.Name, - # $this.DatabaseName, - # $this.InstanceName - # ) - # ) - - foreach ($currentPermission in $properties.Permission) + foreach ($currentPermission in $this.Permission) { try { @@ -408,7 +478,10 @@ class SqlDatabasePermission : ResourceBase } catch { - $errorMessage = $this.localizedData.FailedToSetPermissionDatabase -f $this.Name, $this.DatabaseName + $errorMessage = $this.localizedData.FailedToSetPermissionDatabase -f @( + $this.Name, + $this.DatabaseName + ) New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } @@ -418,9 +491,9 @@ class SqlDatabasePermission : ResourceBase else { $missingPrincipalMessage = $this.localizedData.NameIsMissing -f @( - $properties.Name, - $properties.DatabaseName, - $properties.InstanceName + $this.Name, + $this.DatabaseName, + $this.InstanceName ) New-InvalidOperationException -Message $missingPrincipalMessage diff --git a/source/en-US/SqlDatabasePermission.strings.psd1 b/source/en-US/SqlDatabasePermission.strings.psd1 index afe1275d9..a6e1c2c7f 100644 --- a/source/en-US/SqlDatabasePermission.strings.psd1 +++ b/source/en-US/SqlDatabasePermission.strings.psd1 @@ -11,6 +11,6 @@ ConvertFrom-StringData @' # Strings directly used by the derived class SqlDatabasePermission. EvaluateDatabasePermissionForPrincipal = Evaluate the current permissions for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0001) NameIsMissing = The name '{0}' is neither a database user, database role (user-defined), or database application role in the database '{1}', or the database '{1}' does not exist on the instance '{2}'. (SDP0004) - ChangePermissionForUser = Changing the permission for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0003) + DesiredAbsentPermissionArePresent = The desired permissions that shall be absent are present for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0003) FailedToSetPermissionDatabase = Failed to set the permissions for the user '{0}' in the database '{1}'. (SDP0007) '@ diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index f0b2ff2b7..27ee469a7 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -128,7 +128,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.Get() - $currentState.Ensure | Should -Be 'Present' + $currentState.Ensure | Should -Be ([Ensure]::Present) $currentState.InstanceName | Should -Be 'NamedInstance' $currentState.DatabaseName | Should -Be 'MockDatabaseName' $currentState.Name | Should -Be 'MockUserName' @@ -189,7 +189,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.Get() - $currentState.Ensure | Should -Be 'Present' + $currentState.Ensure | Should -Be ([Ensure]::Present) $currentState.InstanceName | Should -Be 'NamedInstance' $currentState.DatabaseName | Should -Be 'MockDatabaseName' $currentState.Name | Should -Be 'MockUserName' @@ -212,7 +212,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { BeforeAll { InModuleScope -ScriptBlock { $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ - Ensure = 'Absent' + Ensure = [Ensure]::Absent Name = 'MockUserName' DatabaseName = 'MockDatabaseName' InstanceName = 'NamedInstance' @@ -239,7 +239,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.Get() - $currentState.Ensure | Should -Be 'Absent' + $currentState.Ensure | Should -Be ([Ensure]::Absent) $currentState.InstanceName | Should -Be 'NamedInstance' $currentState.DatabaseName | Should -Be 'MockDatabaseName' $currentState.Name | Should -Be 'MockUserName' @@ -441,6 +441,137 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { } } } + + Context 'When a permission should not absent for state Grant' { + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Ensure = [Ensure]::Absent + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Select') + } + ) + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + $mockDatabasePermissionInfo = @() + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + return $mockDatabasePermissionInfo + } + } + + It 'Should return the state as absent and return the correct current state' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Ensure | Should -Be ([Ensure]::Absent) + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 1 + + $currentState.Permission[0].State | Should -Be 'Grant' + $currentState.Permission[0].Permission | Should -Contain 'Connect' + $currentState.Permission[0].Permission | Should -Contain 'Update' + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Ensure = [Ensure]::Absent + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Update') + } + ) + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + $mockDatabasePermissionInfo = @() + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + return $mockDatabasePermissionInfo + } + } + + It 'Should return the state as present and return the correct current state' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 1 + + $currentState.Permission[0].State | Should -Be 'Grant' + $currentState.Permission[0].Permission | Should -Contain 'Connect' + $currentState.Permission[0].Permission | Should -Contain 'Update' + } + } + } + } } Describe 'SqlDatabasePermission\Set()' -Tag 'Set' { From a38dc5fdc7ca4b0be8803418281c02c1ce5a1ecd Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 18 Jul 2022 14:50:17 +0200 Subject: [PATCH 46/98] Add Permisson, PermissionToInclude and PermissionToExclude --- source/Classes/002.DatabasePermission.ps1 | 112 +++- source/Classes/010.ResourceBase.ps1 | 3 + source/Classes/020.SqlDatabasePermission.ps1 | 378 +++++++++----- .../1-GrantDatabasePermissions.ps1 | 71 --- .../1-SetDatabasePermissions.ps1 | 100 ++++ .../2-RevokeDatabasePermissions.ps1 | 54 -- .../3-DenyDatabasePermissions.ps1 | 74 --- source/Private/Get-DscProperty.ps1 | 107 ++++ source/Private/Get-KeyProperty.ps1 | 6 +- source/Private/Test-ResourceHasProperty.ps1 | 30 +- .../en-US/SqlDatabasePermission.strings.psd1 | 6 +- source/en-US/SqlServerDsc.strings.psd1 | 3 + .../Unit/Classes/DatabasePermission.Tests.ps1 | 191 ++++++- .../Classes/SqlDatabasePermission.Tests.ps1 | 14 +- tests/Unit/Private/Get-DscProperty.Tests.ps1 | 489 ++++++++++++++++++ .../Test-ResourceHasProperty.Tests.ps1 | 92 ++++ 16 files changed, 1365 insertions(+), 365 deletions(-) delete mode 100644 source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 create mode 100644 source/Examples/Resources/SqlDatabasePermission/1-SetDatabasePermissions.ps1 delete mode 100644 source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 delete mode 100644 source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 create mode 100644 source/Private/Get-DscProperty.ps1 create mode 100644 tests/Unit/Private/Get-DscProperty.Tests.ps1 diff --git a/source/Classes/002.DatabasePermission.ps1 b/source/Classes/002.DatabasePermission.ps1 index 26a9139dd..cfc20236e 100644 --- a/source/Classes/002.DatabasePermission.ps1 +++ b/source/Classes/002.DatabasePermission.ps1 @@ -9,13 +9,30 @@ The permissions to be granted or denied for the user in the database. .NOTES - The DSC properties specifies attributes Key and Required, those attributes - are not honored during compilation in the current implementation of - PowerShell DSC. They are kept here so when they do get honored it will help - detect missing properties during compilation. The Key property is evaluate - during runtime so that no two states are enforcing the same permission. + The DSC properties specifies the attribute Mandatory but State was meant + to be attribute Key, but those attributes are not honored correctly during + compilation in the current implementation of PowerShell DSC. If the + attribute would have been left as Key then it would not have been possible + to add an identical instance of DatabasePermission in two separate DSC + resource instances in a DSC configuration. The Key property only works + on the top level DSC properties. E.g. two resources instances of + SqlDatabasePermission in a DSC configuration trying to grant the database + permission 'connect' in two separate databases would have failed compilation + as a the property State would have been seen as "duplicate resource". + + Since it is not possible to use the attribute Key the State property is + evaluate during runtime so that no two states are enforcing the same + permission. + + The method Equals() returns $false if type is not [DatabasePermission] + on both sides of the comparison. There was a though to throw an exception + if the object being compared was of another type, but since there was issues + with using [DatabasePermission[]] it was left out. This can be the correct + way since if moving [DatabasePermission[]] to the left side and the + [DatabasePermission] to the right side, then the left side array is filtered + with the matching values on the right side. #> -class DatabasePermission : System.IEquatable[Object] +class DatabasePermission : IComparable, System.IEquatable[Object] { [DscProperty(Mandatory)] [ValidateSet('Grant', 'GrantWithGrant', 'Deny')] @@ -24,6 +41,7 @@ class DatabasePermission : System.IEquatable[Object] # TODO: Can we use a validate set for the permissions? [DscProperty(Mandatory)] + [AllowEmptyCollection()] [System.String[]] $Permission @@ -41,22 +59,80 @@ class DatabasePermission : System.IEquatable[Object] } } } - else + + return $isEqual + } + + + [System.Int32] CompareTo([Object] $object) + { + [System.Int32] $returnValue = 0 + + if ($null -eq $object) + { + return 1 + } + + if ($object -is $this.GetType()) { <# - TODO: Not sure how to handle [DatabasePermission[]], this was meant to - throw for example if the right side was of the comparison was a - [String]. But this would also throw if the the left side was - [DatabasePermission] and the right side was [DatabasePermission[]]. - For now it returns $false if type is not [DatabasePermission] - on both sides of the comparison. This can be the correct way - since if moving [DatabasePermission[]] to the left side and - the [DatabasePermission] to the right side, then the left side - array is filtered with the matching values on the right side. + Less than zero - The current instance precedes the object specified by the CompareTo + method in the sort order. + Zero - This current instance occurs in the same position in the sort order + as the object specified by the CompareTo method. + Greater than zero - This current instance follows the object specified by the CompareTo + method in the sort order. #> - #throw ('Invalid type in comparison. Expected type [{0}], but the type was [{1}].' -f $this.GetType().FullName, $object.GetType().FullName) + $returnValue = 0 + + # Order objects in the order 'Grant', 'GrantWithGrant', 'Deny'. + switch ($this.State) + { + 'Grant' + { + if ($object.State -in @('GrantWithGrant', 'Deny')) + { + # This current instance precedes $object + $returnValue = -1 + } + } + + 'GrantWithGrant' + { + if ($object.State -in @('Grant')) + { + # This current instance follows $object + $returnValue = 1 + } + + if ($object.State -in @('Deny')) + { + # This current instance precedes $object + $returnValue = -1 + } + } + + 'Deny' + { + if ($object.State -in @('Grant', 'GrantWithGrant')) + { + # This current instance follows $object + $returnValue = 1 + } + } + } } + else + { + # TODO: This should be an terminating error as an ArgumentException. + $errorMessage = $script:localizedData.InvalidTypeForCompare -f @( + $this.GetType().FullName, + $object.GetType().FullName + ) - return $isEqual + New-InvalidArgumentException -ArgumentName 'Object' -Message $errorMessage + } + + return $returnValue } } diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index ac036b981..db590fac0 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -30,6 +30,7 @@ class ResourceBase { $this.Assert() + # TODO: Use: Get-DscProperty -Type 'Key' # Get all key properties. $keyProperty = $this | Get-KeyProperty @@ -273,6 +274,8 @@ class ResourceBase Properties = $desiredState.Keys ExcludeProperties = ($excludeProperties + $this.notEnforcedProperties) | Select-Object -Unique IncludeValue = $true + # This is needed to sort complex types. + SortArrayValues = $true } <# diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 4baf41fee..83da781aa 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -84,7 +84,7 @@ ServerName = 'localhost' InstanceName = 'SQL2017' DatabaseName = 'AdventureWorks' - Credential = $SqlInstallCredential + Credential = (Get-Credential -UserName 'myuser@company.local' -Message 'Password:') Name = 'INSTANCE\SqlUser' Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( ( @@ -96,12 +96,20 @@ ( New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ State = 'GrantWithGrant' - Permission = @('update') + Permission = [System.String[]] @() + } + ) + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'Deny' + Permission = [System.String[]] @() } ) ) } + This example shows how to call the resource using Invoke-DscResource. + .NOTES The built-in property `PsDscRunAsCredential` is not supported on this DSC resource as it uses a complex type (another class as the type for a DSC @@ -132,18 +140,25 @@ class SqlDatabasePermission : ResourceBase [System.String] $ServerName = (Get-ComputerName) - # TODO: Should also add IncludePermission and ExcludePermission which should be mutually exclusive from Permission - [DscProperty(Mandatory)] + [DscProperty()] [DatabasePermission[]] $Permission + [DscProperty()] + [DatabasePermission[]] + $PermissionToInclude + + [DscProperty()] + [DatabasePermission[]] + $PermissionToExclude + [DscProperty()] [PSCredential] $Credential - [DscProperty()] - [Ensure] - $Ensure = [Ensure]::Present + # [DscProperty()] + # [Ensure] + # $Ensure = [Ensure]::Present [DscProperty(NotConfigurable)] [Reason[]] @@ -216,7 +231,8 @@ class SqlDatabasePermission : ResourceBase $connectSqlDscDatabaseEngineParameters.Credential = $this.Credential } - $sqlServerObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters + # TODO: By adding a hidden property that holds the server object we only need to connect when that property is $null. + $serverObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters # TODO: TA BORT -VERBOSE! Write-Verbose -Verbose -Message ( @@ -227,7 +243,7 @@ class SqlDatabasePermission : ResourceBase ) ) - $databasePermissionInfo = $sqlServerObject | + $databasePermissionInfo = $serverObject | Get-SqlDscDatabasePermission -DatabaseName $this.DatabaseName -Name $this.Name -ErrorAction 'SilentlyContinue' # If permissions was returned, build the current permission array of [DatabasePermission]. @@ -285,6 +301,18 @@ class SqlDatabasePermission : ResourceBase } } + # Always return all State; 'Grant', 'GrantWithGrant', and 'Deny'. + foreach ($currentPermissionState in @('Grant', 'GrantWithGrant', 'Deny')) + { + if ($currentState.Permission.State -notcontains $currentPermissionState) + { + [DatabasePermission[]] $currentState.Permission += [DatabasePermission] @{ + State = $currentPermissionState + Permission = @() + } + } + } + <# When $this.Ensure is 'Absent' and the node is in desired state the current state permissions will always differ @@ -295,80 +323,80 @@ class SqlDatabasePermission : ResourceBase The base class does not know and cannot know how to evaluate absent permissions correctly. So this needs to evaluated here. #> - if ($this.Ensure -eq [Ensure]::Absent) - { - $inDesiredState = $true - - # Evaluate so that the desired state is missing from the current state. - foreach ($desiredPermission in $this.Permission) - { - $currentStatePermissionForState = $currentState.Permission | - Where-Object -FilterScript { - $_.State -eq $desiredPermission.State - } - - foreach ($desiredPermissionName in $desiredPermission.Permission) - { - if ($currentStatePermissionForState.Permission -contains $desiredPermissionName) - { - Write-Verbose -Message ( - $this.localizedData.DesiredAbsentPermissionArePresent -f @( - $this.Name, - $this.DatabaseName, - $this.InstanceName - ) - ) + # if ($this.Ensure -eq [Ensure]::Absent) + # { + # $inDesiredState = $true - $inDesiredState = $false - } - } - } - - <# - When $this.Ensure is 'Absent' then 'Permission' must be added to - the property $this.notEnforcedProperties so that the Permission - property is not compared. This is especially true when the - permissions are in desired state. But when the permissions are not - in desired state, if the Permission property would be 0compared by - the base class, it would not be intuitive to the user - since the verbose messages output from the base class will say - that it expects the permissions to be that of what should be absent. - #> - $this.notEnforcedProperties += 'Permission' + # # Evaluate so that the desired state is missing from the current state. + # foreach ($desiredPermission in $this.Permission) + # { + # $currentStatePermissionForState = $currentState.Permission | + # Where-Object -FilterScript { + # $_.State -eq $desiredPermission.State + # } + + # foreach ($desiredPermissionName in $desiredPermission.Permission) + # { + # if ($currentStatePermissionForState.Permission -contains $desiredPermissionName) + # { + # Write-Verbose -Message ( + # $this.localizedData.DesiredAbsentPermissionArePresent -f @( + # $this.Name, + # $this.DatabaseName, + # $this.InstanceName + # ) + # ) + + # $inDesiredState = $false + # } + # } + # } - <# - We should also return the property 'Ensure' set to 'Absent' or - 'Present' so the base class does not try to evaluate the state - itself. - #> - if ($inDesiredState) - { - <# - The desired permission that should be absent does not exist - in the current state, therefor we return 'Absent'. - #> - $currentState.Ensure = [Ensure]::Absent - } - else - { - <# - The desired permission that should be absent exist in the current - state, therefor we return 'Present'. - #> - $currentState.Ensure = [Ensure]::Present - } - } - else - { - <# - If the property notEnforcedProperties contains 'Permission', remove it. - This is a fail-safe if the same class instance would be re-used. - #> - if ($this.notEnforcedProperties -contains 'Permission') - { - $this.notEnforcedProperties = @($this.notEnforcedProperties) -ne 'Permission' - } - } + # <# + # When $this.Ensure is 'Absent' then 'Permission' must be added to + # the property $this.notEnforcedProperties so that the Permission + # property is not compared. This is especially true when the + # permissions are in desired state. But when the permissions are not + # in desired state, if the Permission property would be 0compared by + # the base class, it would not be intuitive to the user + # since the verbose messages output from the base class will say + # that it expects the permissions to be that of what should be absent. + # #> + # $this.notEnforcedProperties += 'Permission' + + # <# + # We should also return the property 'Ensure' set to 'Absent' or + # 'Present' so the base class does not try to evaluate the state + # itself. + # #> + # if ($inDesiredState) + # { + # <# + # The desired permission that should be absent does not exist + # in the current state, therefor we return 'Absent'. + # #> + # $currentState.Ensure = [Ensure]::Absent + # } + # else + # { + # <# + # The desired permission that should be absent exist in the current + # state, therefor we return 'Present'. + # #> + # $currentState.Ensure = [Ensure]::Present + # } + # } + # else + # { + # <# + # If the property notEnforcedProperties contains 'Permission', remove it. + # This is a fail-safe if the same class instance would be re-used. + # #> + # if ($this.notEnforcedProperties -contains 'Permission') + # { + # $this.notEnforcedProperties = @($this.notEnforcedProperties) -ne 'Permission' + # } + # } return $currentState } @@ -408,7 +436,12 @@ class SqlDatabasePermission : ResourceBase if ($isDatabasePrincipal) { + $keyProperty = $this | Get-DscProperty -Type 'Key' + + $currentState = $this.GetCurrentState($keyProperty) + <# + TODO: Remove this comment-block. Update permissions if: - $properties contains property Permission @@ -420,16 +453,80 @@ class SqlDatabasePermission : ResourceBase Second will happen when there are permissions in the current state that should be absent. #> - if ($properties.ContainsKey('Permission') -or - ( - $properties.ContainsKey('Ensure') -and - $properties.Ensure -eq [Ensure]::Absent - ) - ) + if ($properties.ContainsKey('Permission')) { foreach ($currentPermission in $this.Permission) { - try + $currentPermissionsForState = $currentState.Permission | + Where-Object -FilterScript { + $_.State -eq $currentPermission.State + } + + # Revoke permissions that are not part of the desired state + if ($currentPermissionsForState) + { + $permissionsToRevoke = @() + + $revokePermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + + foreach ($permissionName in $currentPermissionsForState.Permission) + { + if ($permissionName -notin $currentPermission.Permission) + { + $permissionsToRevoke += $permissionName + + $revokePermissionSet.$permissionName = $true + } + } + + if ($permissionsToRevoke) + { + Write-Verbose -Message ( + $this.localizedData.RevokePermissionNotInDesiredState -f @( + ($permissionsToRevoke -join "', '"), + $this.Name, + $this.DatabaseName + ) + ) + + $setSqlDscDatabasePermissionParameters = @{ + ServerObject = $serverObject + DatabaseName = $this.DatabaseName + Name = $this.Name + Permission = $revokePermissionSet + State = 'Revoke' + } + + if ($currentPermission.State -eq 'GrantWithGrant') + { + $setSqlDscDatabasePermissionParameters.WithGrant = $true + } + + try + { + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters + } + catch + { + $errorMessage = $this.localizedData.FailedToRevokePermissionFromCurrentState -f @( + $this.Name, + $this.DatabaseName + ) + + <# + TODO: Update the CONTRIBUTING.md section "Class-based DSC resource" + that now says that 'throw' should be used.. we should use + helper function instead. Or something similar to commands + where the ID number is part of code? But might be a problem + tracing a specific verbose string down? + #> + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + } + + # If there is not an empty array, change permissions. + if (-not [System.String]::IsNullOrEmpty($currentPermission.Permission)) { $permissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' @@ -445,45 +542,39 @@ class SqlDatabasePermission : ResourceBase Permission = $permissionSet } - switch ($this.Ensure) + try { - 'Present' + switch ($currentPermission.State) { - switch ($currentPermission.State) + 'GrantWithGrant' { - 'GrantWithGrant' - { - Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Grant' -WithGrant - } - - default - { - Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State $currentPermission.State - } + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Grant' -WithGrant } - } - 'Absent' - { - if ($currentPermission.State -eq 'GrantWithGrant') + default { - Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Revoke' -WithGrant - } - else - { - Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Revoke' + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State $currentPermission.State } } + + # if ($currentPermission.State -eq 'GrantWithGrant') + # { + # Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Revoke' -WithGrant + # } + # else + # { + # Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Revoke' + # } } - } - catch - { - $errorMessage = $this.localizedData.FailedToSetPermissionDatabase -f @( - $this.Name, - $this.DatabaseName - ) + catch + { + $errorMessage = $this.localizedData.FailedToSetPermission -f @( + $this.Name, + $this.DatabaseName + ) - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } } } } @@ -508,20 +599,47 @@ class SqlDatabasePermission : ResourceBase { # TODO: Add the evaluation so that one permission can't be added two different states ('Grant' and 'Deny') in the same resource instance. - # TODO: Add the evaluation so that the same State cannot exist several times in the same resource instance. - Write-Verbose -Verbose -Message 'NotImplemented: AssertProperties()' + # PermissionToInclude and PermissionToExclude should be mutually exclusive from Permission + $assertBoundParameterParameters = @{ + BoundParameterList = $this | Get-DscProperty -Type 'Optional' -HasValue + MutuallyExclusiveList1 = @( + 'Permission' + ) + MutuallyExclusiveList2 = @( + 'PermissionToInclude' + 'PermissionToExclude' + ) + } - # @( - # 'DirectoryPartitionAutoEnlistInterval', - # 'TombstoneInterval' - # ) | ForEach-Object -Process { - # $valueToConvert = $this.$_ + Assert-BoundParameter @assertBoundParameterParameters - # # Only evaluate properties that have a value. - # if ($null -ne $valueToConvert) - # { - # Assert-TimeSpan -PropertyName $_ -Value $valueToConvert -Minimum '0.00:00:00' - # } - # } + $isPropertyPermissionAssigned = $this | Test-ResourceHasProperty -Name 'Permission' -HasValue + + if ($isPropertyPermissionAssigned) + { + # One State cannot exist several times in the same resource instance. + $permissionStateGroupCount = @( + $this.Permission | + Group-Object -NoElement -Property 'State' -CaseSensitive:$false | + Select-Object -ExpandProperty 'Count' + ) + + if ($permissionStateGroupCount -gt 1) + { + throw $this.localizedData.DuplicatePermissionState + } + + # Each State must exist once. + $missingPermissionState = ( + $this.Permission.State -notcontains 'Grant' -or + $this.Permission.State -notcontains 'GrantWithGrant' -or + $this.Permission.State -notcontains 'Deny' + ) + + if ($missingPermissionState) + { + throw $this.localizedData.MissingPermissionState + } + } } } diff --git a/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 deleted file mode 100644 index 2d0d03575..000000000 --- a/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -<# - .DESCRIPTION - This example shows how to ensure that the user account CONTOSO\SQLAdmin - has "Connect" and "Update" SQL Permissions for database "AdventureWorks". -#> - -Configuration Example -{ - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName 'SqlServerDsc' - - node localhost - { - SqlDatabasePermission 'Grant_SqlDatabasePermissions_SQLAdmin_Db01' - { - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorks' - Permission = @( - DatabasePermission - { - State = 'Grant' - Permission = @('Connect', 'Update') - } - ) - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Credential = $SqlAdministratorCredential - } - - SqlDatabasePermission 'Grant_SqlDatabasePermissions_SQLUser_Db01' - { - Ensure = 'Present' - Name = 'CONTOSO\SQLUser' - DatabaseName = 'AdventureWorks' - Permission = @( - DatabasePermission - { - State = 'Grant' - Permission = @('Connect', 'Update') - } - ) - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Credential = $SqlAdministratorCredential - } - - SqlDatabasePermission 'Grant_SqlDatabasePermissions_SQLAdmin_Db02' - { - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorksLT' - Permission = @( - DatabasePermission - { - State = 'Grant' - Permission = @('Connect', 'Update') - } - ) - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Credential = $SqlAdministratorCredential - } - } -} diff --git a/source/Examples/Resources/SqlDatabasePermission/1-SetDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/1-SetDatabasePermissions.ps1 new file mode 100644 index 000000000..6dc783bb5 --- /dev/null +++ b/source/Examples/Resources/SqlDatabasePermission/1-SetDatabasePermissions.ps1 @@ -0,0 +1,100 @@ +<# + .DESCRIPTION + This example shows how to ensure that the user account CONTOSO\SQLAdmin + is granted "Connect" and "Update" permissions for the databases DB01 and + DB02. It also shows that the user account CONTOSO\SQLUser has is granted + "Connect" and "Update" permissions, but also how it is denied the permission + "Delete" for the database DB01. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost + { + SqlDatabasePermission 'Set_Database_Permissions_SQLAdmin_DB01' + { + ServerName = 'sql01.company.local' + InstanceName = 'DSC' + DatabaseName = 'DB01' + Name = 'CONTOSO\SQLAdmin' + Credential = $SqlAdministratorCredential + Permission = @( + DatabasePermission + { + State = 'Grant' + Permission = @('Connect', 'Update') + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Deny' + Permission = @() + } + ) + } + + SqlDatabasePermission 'Set_Database_Permissions_SQLAdmin_DB02' + { + ServerName = 'sql01.company.local' + InstanceName = 'DSC' + DatabaseName = 'DB02' + Name = 'CONTOSO\SQLAdmin' + Credential = $SqlAdministratorCredential + Permission = @( + DatabasePermission + { + State = 'Grant' + Permission = @('Connect', 'Update') + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Deny' + Permission = @() + } + ) + } + + SqlDatabasePermission 'Set_Database_Permissions_SQLUser_DB01' + { + ServerName = 'sql01.company.local' + InstanceName = 'DSC' + DatabaseName = 'DB01' + Name = 'CONTOSO\SQLUser' + Credential = $SqlAdministratorCredential + Permission = @( + DatabasePermission + { + State = 'Grant' + Permission = @('Connect', 'Update') + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Deny' + Permission = @('Delete') + } + ) + } + } +} diff --git a/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 deleted file mode 100644 index 7e0159432..000000000 --- a/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 +++ /dev/null @@ -1,54 +0,0 @@ -<# - .DESCRIPTION - This example shows how to ensure that the user account CONTOSO\SQLAdmin - hasn't "Select" and "Create Table" SQL Permissions for database "AdventureWorks". -#> - -Configuration Example -{ - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName 'SqlServerDsc' - - node localhost - { - SqlDatabasePermission 'RevokeGrant_SqlDatabasePermissions_SQLAdmin' - { - Ensure = 'Absent' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorks' - Permission = @( - DatabasePermission - { - State = 'Grant' - Permission = @('Connect', 'Update') - } - ) - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Credential = $SqlAdministratorCredential - } - - SqlDatabasePermission 'RevokeDeny_SqlDatabasePermissions_SQLAdmin' - { - Ensure = 'Absent' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorksLT' - Permission = @( - DatabasePermission - { - State = 'Grant' - Permission = @('Connect', 'Update') - } - ) - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Credential = $SqlAdministratorCredential - } - } -} diff --git a/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 deleted file mode 100644 index d2e8dd9ea..000000000 --- a/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 +++ /dev/null @@ -1,74 +0,0 @@ -<# - .DESCRIPTION - This example shows how to ensure that the user account CONTOSO\SQLAdmin - has "Connect" and "Update" SQL Permissions for database "AdventureWorks". -#> - -Configuration Example -{ - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName 'SqlServerDsc' - - node localhost - { - SqlDatabasePermission 'Deny_SqlDatabasePermissions_SQLAdmin_Db01' - { - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorks' - Permission = @( - DatabasePermission - { - State = 'Deny' - Permission = @('Select', 'CreateTable') - } - ) - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - Credential = $SqlAdministratorCredential - } - - SqlDatabasePermission 'Deny_SqlDatabasePermissions_SQLUser_Db01' - { - Ensure = 'Present' - Name = 'CONTOSO\SQLUser' - DatabaseName = 'AdventureWorks' - Permission = @( - DatabasePermission - { - State = 'Deny' - Permission = @('Select', 'CreateTable') - } - ) - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - Credential = $SqlAdministratorCredential - } - - SqlDatabasePermission 'Deny_SqlDatabasePermissions_SQLAdmin_Db02' - { - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorksLT' - Permission = @( - DatabasePermission - { - State = 'Deny' - Permission = @('Select', 'CreateTable') - } - ) - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - Credential = $SqlAdministratorCredential - } - } -} diff --git a/source/Private/Get-DscProperty.ps1 b/source/Private/Get-DscProperty.ps1 new file mode 100644 index 000000000..3df270933 --- /dev/null +++ b/source/Private/Get-DscProperty.ps1 @@ -0,0 +1,107 @@ + +<# + .SYNOPSIS + Returns all of the DSC resource key properties and their values. + + .DESCRIPTION + Returns all of the DSC resource key properties and their values. + + .PARAMETER InputObject + The object that contain one or more key properties. + + .OUTPUTS + Hashtable +#> +function Get-DscProperty +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject, + + [Parameter()] + [System.String] + $Name, + + [Parameter()] + [ValidateSet('Key', 'Mandatory', 'NotConfigurable', 'Optional')] + [System.String[]] + $Type, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $HasValue + ) + + $property = $InputObject | + Get-Member -MemberType 'Property' | + Select-Object -ExpandProperty 'Name' | + Where-Object -FilterScript { + $InputObject.GetType().GetMember($_).CustomAttributes.Where( + { + $_.AttributeType.Name -eq 'DscPropertyAttribute' + } + ) + } + + # Filter out the specified name. + if ($PSBoundParameters.ContainsKey('Name')) + { + $property = @($property) -eq $Name + } + + if (-not [System.String]::IsNullOrEmpty($property)) + { + if ($PSBoundParameters.ContainsKey('Type')) + { + $propertiesOfType = @() + + $propertiesOfType += $property | Where-Object -FilterScript { + $InputObject.GetType().GetMember($_).CustomAttributes.Where( + { + <# + To simplify the code, ignoring that this will compare + MemberNAme against type 'Optional' which does not exist. + #> + $_.NamedArguments.MemberName -in $Type + } + ).NamedArguments.TypedValue.Value -eq $true + } + + # Include all optional parameter if it was requested. + if ($Type -contains 'Optional') + { + $propertiesOfType += $property | Where-Object -FilterScript { + $InputObject.GetType().GetMember($_).CustomAttributes.Where( + { + $_.NamedArguments.MemberName -notin @('Key', 'Mandatory', 'NotConfigurable') + } + ) + } + } + + $property = $propertiesOfType + } + } + + # Return a hashtable containing each key property and its value. + $getPropertyResult = @{} + + foreach ($currentProperty in $property) + { + if ($HasValue.IsPresent) + { + if ($null -eq $InputObject.$currentProperty) + { + continue + } + } + + $getPropertyResult.$currentProperty = $InputObject.$currentProperty + } + + return $getPropertyResult +} diff --git a/source/Private/Get-KeyProperty.ps1 b/source/Private/Get-KeyProperty.ps1 index d40abd897..ef8d1ff59 100644 --- a/source/Private/Get-KeyProperty.ps1 +++ b/source/Private/Get-KeyProperty.ps1 @@ -1,13 +1,13 @@ <# .SYNOPSIS - Returns the DSC resource key property and its value. + Returns all of the DSC resource key properties and their values. .DESCRIPTION - Returns the DSC resource key property and its value. + Returns all of the DSC resource key properties and their values. .PARAMETER InputObject - The object that contain the key property. + The object that contain one or more key properties. .OUTPUTS Hashtable diff --git a/source/Private/Test-ResourceHasProperty.ps1 b/source/Private/Test-ResourceHasProperty.ps1 index 253fc45cc..abfe86847 100644 --- a/source/Private/Test-ResourceHasProperty.ps1 +++ b/source/Private/Test-ResourceHasProperty.ps1 @@ -12,6 +12,10 @@ .PARAMETER Name Specifies the name of the property. + .PARAMETER HasValue + Specifies if the property should be evaluated to have a non-value. If + the property exist but is assigned `$null` the command returns `$false`. + .OUTPUTS [Boolean] #> @@ -27,13 +31,19 @@ function Test-ResourceHasProperty [Parameter(Mandatory = $true)] [System.String] - $Name + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $HasValue ) - $hasEnsure = $false + $hasProperty = $false + + # TODO: This should call Get-DscProperty instead. # Check to see if the property exist and that is it a DSC property. - $ensureProperty = $InputObject | + $isDscProperty = $InputObject | Get-Member -MemberType 'Property' -Name $Name | Where-Object -FilterScript { $InputObject.GetType().GetMember($_.Name).CustomAttributes.Where( @@ -43,10 +53,18 @@ function Test-ResourceHasProperty ) } - if ($ensureProperty) + if ($isDscProperty) { - $hasEnsure = $true + $hasProperty = $true + + if ($HasValue.IsPresent) + { + if ($null -eq $InputObject.$Name) + { + $hasProperty = $false + } + } } - return $hasEnsure + return $hasProperty } diff --git a/source/en-US/SqlDatabasePermission.strings.psd1 b/source/en-US/SqlDatabasePermission.strings.psd1 index a6e1c2c7f..4869b2127 100644 --- a/source/en-US/SqlDatabasePermission.strings.psd1 +++ b/source/en-US/SqlDatabasePermission.strings.psd1 @@ -12,5 +12,9 @@ ConvertFrom-StringData @' EvaluateDatabasePermissionForPrincipal = Evaluate the current permissions for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0001) NameIsMissing = The name '{0}' is neither a database user, database role (user-defined), or database application role in the database '{1}', or the database '{1}' does not exist on the instance '{2}'. (SDP0004) DesiredAbsentPermissionArePresent = The desired permissions that shall be absent are present for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0003) - FailedToSetPermissionDatabase = Failed to set the permissions for the user '{0}' in the database '{1}'. (SDP0007) + FailedToRevokePermissionFromCurrentState = Failed to revoke the permissions from the current state for the user '{0}' in the database '{1}'. (SDP0007) + FailedToSetPermission = Failed to set the desired permissions for the user '{0}' in the database '{1}'. (SDP0007) + RevokePermissionNotInDesiredState = Revoking permission '{0}' for the user '{1}' in the database '{2}' since the permission is not part of the desired state. (SDP0008) + DuplicatePermissionState = One or more permission states was added more than once. It is only allowed to specify one of each permission state. (SDP0009) + MissingPermissionState = One or more permission states was missing. One of each permission state must be provided. (SDP0009) '@ diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 86dd7e70c..667601581 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -18,4 +18,7 @@ ConvertFrom-StringData @' # Test-SqlDscIsDatabasePrincipal IsDatabasePrincipal_DatabaseMissing = The database '{0}' cannot be found. + + # Class DatabasePermission + InvalidTypeForCompare = Invalid type in comparison. Expected type [{0}], but the type was [{1}]. (DP0001) '@ diff --git a/tests/Unit/Classes/DatabasePermission.Tests.ps1 b/tests/Unit/Classes/DatabasePermission.Tests.ps1 index a7111f2f9..0d488af33 100644 --- a/tests/Unit/Classes/DatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/DatabasePermission.Tests.ps1 @@ -74,7 +74,7 @@ Describe 'DatabasePermission' -Tag 'DatabasePermission' { } } - Context 'When comparing two objects' { + Context 'When comparing two objects using method Equals()' { # TODO: See comment in code regarding the code this test was suppose to cover. # Context 'When the object to compare against is the wrong type' { # It 'Should throw an error on compare' { @@ -181,4 +181,193 @@ Describe 'DatabasePermission' -Tag 'DatabasePermission' { } } } + + Context 'When comparing two objects using method CompareTo()' { + Context 'When the instance is compared against an invalid object' { + It 'Should return a value less than zero' { + $mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { + [DatabasePermission] @{ + State = 'Grant' + Permission = 'Select' + } + } + + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.InvalidTypeForCompare + } + + $mockErrorMessage = $mockErrorMessage -f @( + $mockDatabasePermissionInstance1.GetType().FullName + 'System.String' + ) + + $mockErrorMessage += " (Parameter 'Object')" + + # Escape the brackets so Pester can evaluate the string correctly. + $mockErrorMessage = $mockErrorMessage -replace '\[', '`[' + $mockErrorMessage = $mockErrorMessage -replace '\]', '`]' + + { $mockDatabasePermissionInstance1.CompareTo('AnyValue') } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + Context 'When the instance precedes the object being compared' { + Context 'When the instance has the state '''' and object has state ''''' -ForEach @( + @{ + MockInstanceState = 'Grant' + MockObjectState = 'GrantWithGrant' + } + @{ + MockInstanceState = 'Grant' + MockObjectState = 'Deny' + } + @{ + MockInstanceState = 'GrantWithGrant' + MockObjectState = 'Deny' + } + ) { + It 'Should return a value less than zero' { + $mockDatabasePermissionInstance1 = InModuleScope -Parameters $_ -ScriptBlock { + [DatabasePermission] @{ + State = $MockInstanceState + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance2 = InModuleScope -Parameters $_ -ScriptBlock { + [DatabasePermission] @{ + State = $MockObjectState + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance1.CompareTo($mockDatabasePermissionInstance2) | Should -BeLessThan 0 + } + } + } + + Context 'When the instance follows the object being compared' { + Context 'When the instance has the state '''' and object has state ''''' -ForEach @( + @{ + MockInstanceState = 'Deny' + MockObjectState = 'Grant' + } + @{ + MockInstanceState = 'GrantWithGrant' + MockObjectState = 'Grant' + } + @{ + MockInstanceState = 'Deny' + MockObjectState = 'GrantWithGrant' + } + ) { + It 'Should return a value less than zero' { + $mockDatabasePermissionInstance1 = InModuleScope -Parameters $_ -ScriptBlock { + [DatabasePermission] @{ + State = $MockInstanceState + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance2 = InModuleScope -Parameters $_ -ScriptBlock { + [DatabasePermission] @{ + State = $MockObjectState + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance1.CompareTo($mockDatabasePermissionInstance2) | Should -BeGreaterThan 0 + } + } + + Context 'When the instance is compared against an object that is $null' { + It 'Should return a value less than zero' { + $mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { + [DatabasePermission] @{ + State = 'Grant' + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance1.CompareTo($null) | Should -BeGreaterThan 0 + } + } + } + + Context 'When the instance is in the same position as the object being compared' { + Context 'When the instance has the state '''' and object has state ''''' -ForEach @( + @{ + MockInstanceState = 'Grant' + MockObjectState = 'Grant' + } + @{ + MockInstanceState = 'GrantWithGrant' + MockObjectState = 'GrantWithGrant' + } + @{ + MockInstanceState = 'Deny' + MockObjectState = 'Deny' + } + ) { + It 'Should return a value less than zero' { + $mockDatabasePermissionInstance1 = InModuleScope -Parameters $_ -ScriptBlock { + [DatabasePermission] @{ + State = $MockInstanceState + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance2 = InModuleScope -Parameters $_ -ScriptBlock { + [DatabasePermission] @{ + State = $MockObjectState + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance1.CompareTo($mockDatabasePermissionInstance2) | Should -Be 0 + } + } + } + + Context 'When sorting the instances' { + It 'Should always sort in the correct order' -ForEach @( + @{ + MockState = @('Grant', 'GrantWithGrant', 'Deny') + } + @{ + MockState = @('GrantWithGrant', 'Grant', 'Deny') + } + @{ + MockState = @('GrantWithGrant', 'Deny', 'Grant') + } + @{ + MockState = @('Deny', 'GrantWithGrant', 'Grant') + } + @{ + MockState = @('Grant', 'Deny', 'GrantWithGrant') + } + @{ + MockState = @('Deny', 'Grant', 'GrantWithGrant') + } + ) { + $mockDatabasePermissionArray = @( + InModuleScope -Parameters $_ -ScriptBlock { + foreach ($currentMockState in $MockState) + { + [DatabasePermission] @{ + State = $currentMockState + Permission = 'Select' + } + } + } + ) + + $mockSortedArray = $mockDatabasePermissionArray | Sort-Object + + $mockSortedArray[0].State | Should -Be 'Grant' + $mockSortedArray[1].State | Should -Be 'GrantWithGrant' + $mockSortedArray[2].State | Should -Be 'Deny' + } + } + } } diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index 27ee469a7..0e591956b 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -128,7 +128,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.Get() - $currentState.Ensure | Should -Be ([Ensure]::Present) + #$currentState.Ensure | Should -Be ([Ensure]::Present) $currentState.InstanceName | Should -Be 'NamedInstance' $currentState.DatabaseName | Should -Be 'MockDatabaseName' $currentState.Name | Should -Be 'MockUserName' @@ -189,7 +189,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.Get() - $currentState.Ensure | Should -Be ([Ensure]::Present) + #$currentState.Ensure | Should -Be ([Ensure]::Present) $currentState.InstanceName | Should -Be 'NamedInstance' $currentState.DatabaseName | Should -Be 'MockDatabaseName' $currentState.Name | Should -Be 'MockUserName' @@ -212,7 +212,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { BeforeAll { InModuleScope -ScriptBlock { $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ - Ensure = [Ensure]::Absent + #Ensure = [Ensure]::Absent Name = 'MockUserName' DatabaseName = 'MockDatabaseName' InstanceName = 'NamedInstance' @@ -239,7 +239,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.Get() - $currentState.Ensure | Should -Be ([Ensure]::Absent) + #$currentState.Ensure | Should -Be ([Ensure]::Absent) $currentState.InstanceName | Should -Be 'NamedInstance' $currentState.DatabaseName | Should -Be 'MockDatabaseName' $currentState.Name | Should -Be 'MockUserName' @@ -447,7 +447,7 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { BeforeAll { InModuleScope -ScriptBlock { $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ - Ensure = [Ensure]::Absent + #Ensure = [Ensure]::Absent Name = 'MockUserName' DatabaseName = 'MockDatabaseName' InstanceName = 'NamedInstance' @@ -495,7 +495,7 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { InstanceName = 'NamedInstance' }) - $currentState.Ensure | Should -Be ([Ensure]::Absent) + #$currentState.Ensure | Should -Be ([Ensure]::Absent) $currentState.Credential | Should -BeNullOrEmpty $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' @@ -512,7 +512,7 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { BeforeAll { InModuleScope -ScriptBlock { $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ - Ensure = [Ensure]::Absent + #Ensure = [Ensure]::Absent Name = 'MockUserName' DatabaseName = 'MockDatabaseName' InstanceName = 'NamedInstance' diff --git a/tests/Unit/Private/Get-DscProperty.Tests.ps1 b/tests/Unit/Private/Get-DscProperty.Tests.ps1 new file mode 100644 index 000000000..06311252c --- /dev/null +++ b/tests/Unit/Private/Get-DscProperty.Tests.ps1 @@ -0,0 +1,489 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-DscProperty' -Tag 'Private' { + Context 'When getting all DSC properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -HaveCount 5 + $result.Keys | Should -Contain 'MyResourceKeyProperty1' + $result.Keys | Should -Contain 'MyResourceKeyProperty2' + $result.Keys | Should -Contain 'MyResourceMandatoryProperty' + $result.Keys | Should -Contain 'MyResourceProperty' + $result.Keys | Should -Contain 'MyResourceReadProperty' + + $result.MyResourceKeyProperty1 | Should -Be 'MockValue1' + $result.MyResourceKeyProperty2 | Should -Be 'MockValue2' + $result.MyResourceMandatoryProperty | Should -Be 'MockValue3' + $result.MyResourceProperty | Should -Be 'MockValue4' + $result.MyResourceReadProperty | Should -BeNullOrEmpty + } + } + } + + Context 'When using parameter Type' { + Context 'When getting all key properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Type 'Key' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceProperty' -Because 'optional properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceMandatoryProperty' -Because 'mandatory properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' + + $result.Keys | Should -Contain 'MyResourceKeyProperty1' -Because 'the property is a key property' + $result.Keys | Should -Contain 'MyResourceKeyProperty2' -Because 'the property is a key property' + + $result.MyResourceKeyProperty1 | Should -Be 'MockValue1' + $result.MyResourceKeyProperty2 | Should -Be 'MockValue2' + } + } + } + + Context 'When getting all mandatory properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Type 'Mandatory' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty2' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceProperty' -Because 'optional properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' + + $result.Keys | Should -Contain 'MyResourceMandatoryProperty' -Because 'the property is a mandatory property' + + $result.MyResourceMandatoryProperty | Should -Be 'MockValue3' + } + } + } + + Context 'When getting all optional properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Type 'Optional' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceMandatoryProperty' -Because 'mandatory properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty2' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' + + $result.Keys | Should -Contain 'MyResourceProperty' -Because 'the property is a optional property' + + $result.MyResourceProperty | Should -Be 'MockValue4' + } + } + } + + Context 'When getting all read properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Type 'NotConfigurable' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceProperty' -Because 'optional properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceMandatoryProperty' -Because 'mandatory properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty2' -Because 'key properties should not be part of the collection' + + $result.Keys | Should -Contain 'MyResourceReadProperty' -Because 'the property is a read property' + + $result.MyResourceReadProperty | Should -BeNullOrEmpty + } + } + } + + Context 'When getting all optional and mandatory properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Type @('Mandatory', 'Optional') -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty2' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' + + $result.Keys | Should -Contain 'MyResourceMandatoryProperty' -Because 'the property is a mandatory property' + $result.Keys | Should -Contain 'MyResourceProperty' -Because 'the property is a optional property' + + $result.MyResourceMandatoryProperty | Should -Be 'MockValue3' + $result.MyResourceProperty | Should -Be 'MockValue4' + } + } + } + + } + + Context 'When using parameter HasValue' { + Context 'When getting all optional properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty2 = 'MockValue5' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Type 'Optional' -HasValue -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceMandatoryProperty' -Because 'mandatory properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty2' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'optional properties should not be part of the collection' + + $result.Keys | Should -Not -Contain 'MyResourceProperty1' -Because 'the property has a $null value' + + $result.Keys | Should -Contain 'MyResourceProperty2' -Because 'the property has a non-null value' + + $result.MyResourceProperty2 | Should -Be 'MockValue5' + } + } + } + } +} diff --git a/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 b/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 index 484ee79b5..f0b6b2198 100644 --- a/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 +++ b/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 @@ -173,4 +173,96 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() } } } + + Context 'When using parameter HasValue' { + Context 'When DSC property has a non-null value' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty()] + [System.String] + $MyProperty3 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource] @{ + MyProperty3 = 'AnyValue' +} +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceHasProperty -Name 'MyProperty3' -HasValue -InputObject $script:mockResourceBaseInstance + + $result | Should -BeTrue + } + } + } + } + + Context 'When DSC property has a non-null value' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty1 + +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty2 + +[DscProperty()] +[System.String] +$MyProperty3 + +[DscProperty(NotConfigurable)] +[System.String] +$MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource] @{} +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceHasProperty -Name 'MyProperty3' -HasValue -InputObject $script:mockResourceBaseInstance + + $result | Should -BeFalse + } + } + } } From dc72ac3083d2dd450da9c3de91bc8b7e373d3e4b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 19 Jul 2022 16:15:24 +0200 Subject: [PATCH 47/98] Fix Include and Exclude --- source/Classes/010.ResourceBase.ps1 | 4 + source/Classes/020.SqlDatabasePermission.ps1 | 187 ++++++++++-------- source/Private/Get-DscProperty.ps1 | 4 +- .../Test-ResourcePropertyIsAssigned.ps1 | 35 ++++ .../en-US/SqlDatabasePermission.strings.psd1 | 3 +- .../Test-ResourcePropertyIsAssigned.Tests.ps1 | 136 +++++++++++++ 6 files changed, 282 insertions(+), 87 deletions(-) create mode 100644 source/Private/Test-ResourcePropertyIsAssigned.ps1 create mode 100644 tests/Unit/Private/Test-ResourcePropertyIsAssigned.Tests.ps1 diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index db590fac0..a8e96340a 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -112,6 +112,8 @@ class ResourceBase #> if (($this | Test-ResourceHasProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) { + # TODO: This should evaluate if the key properties is in desired state, if so set Present, otherwise set Absent + if (($propertiesNotInDesiredState -and $this.Ensure -eq [Ensure]::Present) -or (-not $propertiesNotInDesiredState -and $this.Ensure -eq [Ensure]::Absent)) { $dscResourceObject.Ensure = [Ensure]::Absent @@ -132,6 +134,8 @@ class ResourceBase #> if (($this | Test-ResourceHasProperty -Name 'Reasons') -and -not $getCurrentStateResult.ContainsKey('Reasons')) { + # TODO: Below should be a private function ConvertTo-Reason. + # Always return an empty array if all properties are in desired state. $dscResourceObject.Reasons = [Reason[]] @() diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 83da781aa..8e7500fd8 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -313,90 +313,105 @@ class SqlDatabasePermission : ResourceBase } } - <# - When $this.Ensure is 'Absent' and the node is in desired - state the current state permissions will always differ - from desired state permissions, since the current permission - should never have the permission that desired state says - should be removed. - - The base class does not know and cannot know how to evaluate - absent permissions correctly. So this needs to evaluated here. - #> - # if ($this.Ensure -eq [Ensure]::Absent) - # { - # $inDesiredState = $true - - # # Evaluate so that the desired state is missing from the current state. - # foreach ($desiredPermission in $this.Permission) - # { - # $currentStatePermissionForState = $currentState.Permission | - # Where-Object -FilterScript { - # $_.State -eq $desiredPermission.State - # } - - # foreach ($desiredPermissionName in $desiredPermission.Permission) - # { - # if ($currentStatePermissionForState.Permission -contains $desiredPermissionName) - # { - # Write-Verbose -Message ( - # $this.localizedData.DesiredAbsentPermissionArePresent -f @( - # $this.Name, - # $this.DatabaseName, - # $this.InstanceName - # ) - # ) - - # $inDesiredState = $false - # } - # } - # } - - # <# - # When $this.Ensure is 'Absent' then 'Permission' must be added to - # the property $this.notEnforcedProperties so that the Permission - # property is not compared. This is especially true when the - # permissions are in desired state. But when the permissions are not - # in desired state, if the Permission property would be 0compared by - # the base class, it would not be intuitive to the user - # since the verbose messages output from the base class will say - # that it expects the permissions to be that of what should be absent. - # #> - # $this.notEnforcedProperties += 'Permission' - - # <# - # We should also return the property 'Ensure' set to 'Absent' or - # 'Present' so the base class does not try to evaluate the state - # itself. - # #> - # if ($inDesiredState) - # { - # <# - # The desired permission that should be absent does not exist - # in the current state, therefor we return 'Absent'. - # #> - # $currentState.Ensure = [Ensure]::Absent - # } - # else - # { - # <# - # The desired permission that should be absent exist in the current - # state, therefor we return 'Present'. - # #> - # $currentState.Ensure = [Ensure]::Present - # } - # } - # else - # { - # <# - # If the property notEnforcedProperties contains 'Permission', remove it. - # This is a fail-safe if the same class instance would be re-used. - # #> - # if ($this.notEnforcedProperties -contains 'Permission') - # { - # $this.notEnforcedProperties = @($this.notEnforcedProperties) -ne 'Permission' - # } - # } + $isPropertyPermissionToIncludeAssigned = $this | Test-ResourcePropertyIsAssigned -Name 'PermissionToInclude' + + if ($isPropertyPermissionToIncludeAssigned) + { + $currentState.PermissionToInclude = [DatabasePermission[]] @() + + # Evaluate so that the desired state is present in the current state. + foreach ($desiredIncludePermission in $this.PermissionToInclude) + { + <# + Current state will always have all possible states, so this + will always return one item. + #> + $currentStatePermissionForState = $currentState.Permission | + Where-Object -FilterScript { + $_.State -eq $desiredIncludePermission.State + } + + $currentStatePermissionToInclude = [DatabasePermission] @{ + State = $desiredIncludePermission.State + Permission = @() + } + + foreach ($desiredIncludePermissionName in $desiredIncludePermission.Permission) + { + if ($currentStatePermissionForState.Permission -contains $desiredIncludePermissionName) + { + <# + If the permission exist in the current state, add the + permission to $currentState.PermissionToInclude so that + the base class's method Compare() sees the property as + being in desired state (when the property PermissionToInclude + in the current state and desired state are equal). + #> + $currentStatePermissionToInclude.Permission += $desiredIncludePermissionName + } + else + { + Write-Verbose -Message ( + $this.localizedData.DesiredPermissionAreAbsent -f @( + $desiredIncludePermissionName + ) + ) + } + } + + [DatabasePermission[]] $currentState.PermissionToInclude += $currentStatePermissionToInclude + } + } + + $isPropertyPermissionToExcludeAssigned = $this | Test-ResourcePropertyIsAssigned -Name 'PermissionToExclude' + + if ($isPropertyPermissionToExcludeAssigned) + { + $currentState.PermissionToExclude = [DatabasePermission[]] @() + + # Evaluate so that the desired state is missing from the current state. + foreach ($desiredExcludePermission in $this.PermissionToExclude) + { + <# + Current state will always have all possible states, so this + will always return one item. + #> + $currentStatePermissionForState = $currentState.Permission | + Where-Object -FilterScript { + $_.State -eq $desiredExcludePermission.State + } + + $currentStatePermissionToExclude = [DatabasePermission] @{ + State = $desiredExcludePermission.State + Permission = @() + } + + foreach ($desiredExcludedPermissionName in $desiredExcludePermission.Permission) + { + if ($currentStatePermissionForState.Permission -contains $desiredExcludedPermissionName) + { + Write-Verbose -Message ( + $this.localizedData.DesiredAbsentPermissionArePresent -f @( + $desiredExcludedPermissionName + ) + ) + } + else + { + <# + If the permission does _not_ exist in the current state, add + the permission to $currentState.PermissionToExclude so that + the base class's method Compare() sees the property as being + in desired state (when the property PermissionToExclude in + the current state and desired state are equal). + #> + $currentStatePermissionToExclude.Permission += $desiredExcludedPermissionName + } + } + + [DatabasePermission[]] $currentState.PermissionToExclude += $currentStatePermissionToExclude + } + } return $currentState } @@ -613,7 +628,7 @@ class SqlDatabasePermission : ResourceBase Assert-BoundParameter @assertBoundParameterParameters - $isPropertyPermissionAssigned = $this | Test-ResourceHasProperty -Name 'Permission' -HasValue + $isPropertyPermissionAssigned = $this | Test-ResourcePropertyIsAssigned -Name 'Permission' if ($isPropertyPermissionAssigned) { @@ -641,5 +656,7 @@ class SqlDatabasePermission : ResourceBase throw $this.localizedData.MissingPermissionState } } + + # TODO: PermissionToInclude and PermissionToExclude must not contain an empty collection for property Permission } } diff --git a/source/Private/Get-DscProperty.ps1 b/source/Private/Get-DscProperty.ps1 index 3df270933..4a3f6397a 100644 --- a/source/Private/Get-DscProperty.ps1 +++ b/source/Private/Get-DscProperty.ps1 @@ -94,7 +94,9 @@ function Get-DscProperty { if ($HasValue.IsPresent) { - if ($null -eq $InputObject.$currentProperty) + $isAssigned = Test-ResourcePropertyIsAssigned -Name $currentProperty -InputObject $InputObject + + if (-not $isAssigned) { continue } diff --git a/source/Private/Test-ResourcePropertyIsAssigned.ps1 b/source/Private/Test-ResourcePropertyIsAssigned.ps1 new file mode 100644 index 000000000..daddca646 --- /dev/null +++ b/source/Private/Test-ResourcePropertyIsAssigned.ps1 @@ -0,0 +1,35 @@ +<# + .SYNOPSIS + Tests wether the class-based resource property is assigned a non-null value. + + .DESCRIPTION + Tests wether the class-based resource property is assigned a non-null value. + + .PARAMETER InputObject + Specifies the object that contain the property. + + .PARAMETER Name + Specifies the name of the property. + + .OUTPUTS + [Boolean] +#> +function Test-ResourcePropertyIsAssigned +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $isAssigned = -not $null -eq $InputObject.$Name + + return $isAssigned +} diff --git a/source/en-US/SqlDatabasePermission.strings.psd1 b/source/en-US/SqlDatabasePermission.strings.psd1 index 4869b2127..f7335f2f8 100644 --- a/source/en-US/SqlDatabasePermission.strings.psd1 +++ b/source/en-US/SqlDatabasePermission.strings.psd1 @@ -11,7 +11,8 @@ ConvertFrom-StringData @' # Strings directly used by the derived class SqlDatabasePermission. EvaluateDatabasePermissionForPrincipal = Evaluate the current permissions for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0001) NameIsMissing = The name '{0}' is neither a database user, database role (user-defined), or database application role in the database '{1}', or the database '{1}' does not exist on the instance '{2}'. (SDP0004) - DesiredAbsentPermissionArePresent = The desired permissions that shall be absent are present for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0003) + DesiredAbsentPermissionArePresent = The desired permission '{0}' that shall be absent are present. (SDP0003) + DesiredPermissionAreAbsent = The desired permission '{0}' that shall be present are absent. (SDP0003) FailedToRevokePermissionFromCurrentState = Failed to revoke the permissions from the current state for the user '{0}' in the database '{1}'. (SDP0007) FailedToSetPermission = Failed to set the desired permissions for the user '{0}' in the database '{1}'. (SDP0007) RevokePermissionNotInDesiredState = Revoking permission '{0}' for the user '{1}' in the database '{2}' since the permission is not part of the desired state. (SDP0008) diff --git a/tests/Unit/Private/Test-ResourcePropertyIsAssigned.Tests.ps1 b/tests/Unit/Private/Test-ResourcePropertyIsAssigned.Tests.ps1 new file mode 100644 index 000000000..f58396dba --- /dev/null +++ b/tests/Unit/Private/Test-ResourcePropertyIsAssigned.Tests.ps1 @@ -0,0 +1,136 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Test-ResourcePropertyIsAssigned' -Tag 'Private' { + Context 'When DSC property has a non-null value' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty()] + [System.String] + $MyProperty3 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource] @{ + MyProperty3 = 'AnyValue' +} +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourcePropertyIsAssigned -Name 'MyProperty3' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeTrue + } + } + } + + + Context 'When DSC property has a non-null value' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource +{ +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty1 + +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty2 + +[DscProperty()] +[System.String] +$MyProperty3 + +[DscProperty(NotConfigurable)] +[System.String] +$MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource] @{} +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourcePropertyIsAssigned -Name 'MyProperty3' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeFalse + } + } + } +} From 9c71edac3911d53d4c21a3eadd42c54a45f88225 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 20 Jul 2022 13:13:34 +0200 Subject: [PATCH 48/98] Fix working code --- source/Classes/020.SqlDatabasePermission.ps1 | 304 ++++++++++-------- source/Private/Get-DscProperty.ps1 | 19 +- .../en-US/SqlDatabasePermission.strings.psd1 | 2 +- .../Classes/SqlDatabasePermission.Tests.ps1 | 253 ++++++--------- .../Get-DesiredStateProperty.Tests.ps1 | 2 - tests/Unit/Private/Get-DscProperty.Tests.ps1 | 296 ++++++++++++++++- tests/Unit/Private/Get-KeyProperty.Tests.ps1 | 2 - .../Test-ResourceHasProperty.Tests.ps1 | 10 - .../Test-ResourcePropertyIsAssigned.Tests.ps1 | 4 - 9 files changed, 577 insertions(+), 315 deletions(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 8e7500fd8..4282861e7 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -449,161 +449,188 @@ class SqlDatabasePermission : ResourceBase # This will test wether the database and the principal exist. $isDatabasePrincipal = Test-SqlDscIsDatabasePrincipal @testSqlDscIsDatabasePrincipalParameters - if ($isDatabasePrincipal) + if (-not $isDatabasePrincipal) { - $keyProperty = $this | Get-DscProperty -Type 'Key' + $missingPrincipalMessage = $this.localizedData.NameIsMissing -f @( + $this.Name, + $this.DatabaseName, + $this.InstanceName + ) - $currentState = $this.GetCurrentState($keyProperty) + New-InvalidOperationException -Message $missingPrincipalMessage + } - <# - TODO: Remove this comment-block. - Update permissions if: + # This holds each state and their permissions to be revoked. + [DatabasePermission[]] $permissionsToRevoke = @() + [DatabasePermission[]] $permissionsToGrantOrDeny = @() - - $properties contains property Permission - - $properties contains property Ensure and it is set to 'Absent' + if ($properties.ContainsKey('Permission')) + { + $keyProperty = $this | Get-DscProperty -Type 'Key' - First will happen when there are additional permissions to add - to the current state. + $currentState = $this.GetCurrentState($keyProperty) - Second will happen when there are permissions in the current state - that should be absent. + <# + Evaluate if there are any permissions that should be revoked + from the current state. #> - if ($properties.ContainsKey('Permission')) + foreach ($currentDesiredPermissionState in $this.Permission) { - foreach ($currentPermission in $this.Permission) - { - $currentPermissionsForState = $currentState.Permission | - Where-Object -FilterScript { - $_.State -eq $currentPermission.State - } + $currentPermissionsForState = $currentState.Permission | + Where-Object -FilterScript { + $_.State -eq $currentDesiredPermissionState.State + } - # Revoke permissions that are not part of the desired state - if ($currentPermissionsForState) + foreach ($permissionName in $currentPermissionsForState.Permission) + { + if ($permissionName -notin $currentDesiredPermissionState.Permission) { - $permissionsToRevoke = @() - - $revokePermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + # Look for an existing object in the array. + $updatePermissionToRevoke = $permissionsToRevoke | + Where-Object -FilterScript { + $_.State -eq $currentDesiredPermissionState.State + } - foreach ($permissionName in $currentPermissionsForState.Permission) + # Update the existing object in the array, or create a new object + if ($updatePermissionToRevoke) { - if ($permissionName -notin $currentPermission.Permission) - { - $permissionsToRevoke += $permissionName - - $revokePermissionSet.$permissionName = $true + $updatePermissionToRevoke.Permission += $permissionName + } + else + { + [DatabasePermission[]] $permissionsToRevoke += [DatabasePermission] @{ + State = $currentPermissionsForState.State + Permission = $permissionName } } + } + } + } - if ($permissionsToRevoke) - { - Write-Verbose -Message ( - $this.localizedData.RevokePermissionNotInDesiredState -f @( - ($permissionsToRevoke -join "', '"), - $this.Name, - $this.DatabaseName - ) - ) + <# + At least one permission were missing or should have not be present + in the current state. Grant or Deny all permission assigned to the + property Permission regardless if they were already present or not. + #> + [DatabasePermission[]] $permissionsToGrantOrDeny = $this.Permission + } - $setSqlDscDatabasePermissionParameters = @{ - ServerObject = $serverObject - DatabaseName = $this.DatabaseName - Name = $this.Name - Permission = $revokePermissionSet - State = 'Revoke' - } + if ($properties.ContainsKey('PermissionToExclude')) + { + <# + At least one permission were present in the current state. Revoke + all permission assigned to the property PermissionToExclude + regardless if they were already revoked or not. + #> + [DatabasePermission[]] $permissionsToRevoke = $this.PermissionToExclude + } - if ($currentPermission.State -eq 'GrantWithGrant') - { - $setSqlDscDatabasePermissionParameters.WithGrant = $true - } + if ($properties.ContainsKey('PermissionToInclude')) + { + <# + At least one permission were missing or should have not be present + in the current state. Grant or Deny all permission assigned to the + property Permission regardless if they were already present or not. + #> + [DatabasePermission[]] $permissionsToGrantOrDeny = $this.PermissionToInclude + } - try - { - Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters - } - catch - { - $errorMessage = $this.localizedData.FailedToRevokePermissionFromCurrentState -f @( - $this.Name, - $this.DatabaseName - ) - - <# - TODO: Update the CONTRIBUTING.md section "Class-based DSC resource" - that now says that 'throw' should be used.. we should use - helper function instead. Or something similar to commands - where the ID number is part of code? But might be a problem - tracing a specific verbose string down? - #> - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - } - } + # Revoke all the permissions set in $permissionsToRevoke + if ($permissionsToRevoke) + { + foreach ($currentStateToRevoke in $permissionsToRevoke) + { + $revokePermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' - # If there is not an empty array, change permissions. - if (-not [System.String]::IsNullOrEmpty($currentPermission.Permission)) - { - $permissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + foreach ($revokePermissionName in $currentStateToRevoke.Permission) + { + $revokePermissionSet.$revokePermissionName = $true + } - foreach ($permissionName in $currentPermission.Permission) - { - $permissionSet.$permissionName = $true - } + $setSqlDscDatabasePermissionParameters = @{ + ServerObject = $serverObject + DatabaseName = $this.DatabaseName + Name = $this.Name + Permission = $revokePermissionSet + State = 'Revoke' + } - $setSqlDscDatabasePermissionParameters = @{ - ServerObject = $serverObject - DatabaseName = $this.DatabaseName - Name = $this.Name - Permission = $permissionSet - } + if ($currentStateToRevoke.State -eq 'GrantWithGrant') + { + $setSqlDscDatabasePermissionParameters.WithGrant = $true + } - try + try + { + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters + } + catch + { + $errorMessage = $this.localizedData.FailedToRevokePermissionFromCurrentState -f @( + $this.Name, + $this.DatabaseName + ) + + <# + TODO: Update the CONTRIBUTING.md section "Class-based DSC resource" + that now says that 'throw' should be used.. we should use + helper function instead. Or something similar to commands + where the ID number is part of code? But might be a problem + tracing a specific verbose string down? + #> + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + } + + if ($permissionsToGrantOrDeny) + { + foreach ($currentDesiredPermissionState in $permissionsToGrantOrDeny) + { + # If there is not an empty array, change permissions. + if (-not [System.String]::IsNullOrEmpty($currentDesiredPermissionState.Permission)) + { + $permissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + + foreach ($permissionName in $currentDesiredPermissionState.Permission) + { + $permissionSet.$permissionName = $true + } + + $setSqlDscDatabasePermissionParameters = @{ + ServerObject = $serverObject + DatabaseName = $this.DatabaseName + Name = $this.Name + Permission = $permissionSet + } + + try + { + switch ($currentDesiredPermissionState.State) { - switch ($currentPermission.State) + 'GrantWithGrant' { - 'GrantWithGrant' - { - Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Grant' -WithGrant - } - - default - { - Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State $currentPermission.State - } + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Grant' -WithGrant } - # if ($currentPermission.State -eq 'GrantWithGrant') - # { - # Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Revoke' -WithGrant - # } - # else - # { - # Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Revoke' - # } + default + { + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State $currentDesiredPermissionState.State + } } - catch - { - $errorMessage = $this.localizedData.FailedToSetPermission -f @( - $this.Name, - $this.DatabaseName - ) + } + catch + { + $errorMessage = $this.localizedData.FailedToSetPermission -f @( + $this.Name, + $this.DatabaseName + ) - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } } } } - else - { - $missingPrincipalMessage = $this.localizedData.NameIsMissing -f @( - $this.Name, - $this.DatabaseName, - $this.InstanceName - ) - - New-InvalidOperationException -Message $missingPrincipalMessage - } } <# @@ -614,6 +641,8 @@ class SqlDatabasePermission : ResourceBase { # TODO: Add the evaluation so that one permission can't be added two different states ('Grant' and 'Deny') in the same resource instance. + # TODO: PermissionToInclude and PermissionToExclude must not contain an empty collection for property Permission + # PermissionToInclude and PermissionToExclude should be mutually exclusive from Permission $assertBoundParameterParameters = @{ BoundParameterList = $this | Get-DscProperty -Type 'Optional' -HasValue @@ -628,22 +657,40 @@ class SqlDatabasePermission : ResourceBase Assert-BoundParameter @assertBoundParameterParameters - $isPropertyPermissionAssigned = $this | Test-ResourcePropertyIsAssigned -Name 'Permission' + # Get all assigned permission properties. + $assignedPermissionProperty = @( + $this | Get-DscProperty -HasValue -Name @( + 'Permission', + 'PermissionToInclude', + 'PermissionToExclude' + ) + ) - if ($isPropertyPermissionAssigned) + # Must include either of the permission properties. + if (-not $assignedPermissionProperty) + { + # TODO: Should throw ArgumentException + throw $this.localizedData.MustAssignOnePermissionProperty + } + + # One State cannot exist several times in the same resource instance. + foreach ($currentAssignedPermissionProperty in $assignedPermissionProperty) { - # One State cannot exist several times in the same resource instance. $permissionStateGroupCount = @( - $this.Permission | + $this.$currentAssignedPermissionProperty | Group-Object -NoElement -Property 'State' -CaseSensitive:$false | Select-Object -ExpandProperty 'Count' ) if ($permissionStateGroupCount -gt 1) { + # TODO: Should throw ArgumentException throw $this.localizedData.DuplicatePermissionState } + } + if ($assignedPermissionProperty -contains 'Permission') + { # Each State must exist once. $missingPermissionState = ( $this.Permission.State -notcontains 'Grant' -or @@ -653,10 +700,9 @@ class SqlDatabasePermission : ResourceBase if ($missingPermissionState) { + # TODO: Should throw ArgumentException throw $this.localizedData.MissingPermissionState } } - - # TODO: PermissionToInclude and PermissionToExclude must not contain an empty collection for property Permission } } diff --git a/source/Private/Get-DscProperty.ps1 b/source/Private/Get-DscProperty.ps1 index 4a3f6397a..5fc1db2ef 100644 --- a/source/Private/Get-DscProperty.ps1 +++ b/source/Private/Get-DscProperty.ps1 @@ -23,7 +23,7 @@ function Get-DscProperty $InputObject, [Parameter()] - [System.String] + [System.String[]] $Name, [Parameter()] @@ -40,6 +40,13 @@ function Get-DscProperty Get-Member -MemberType 'Property' | Select-Object -ExpandProperty 'Name' | Where-Object -FilterScript { + <# + Return all properties if $Name is not assigned, or if assigned + just those properties. + #> + (-not $Name -or $_ -in $Name) -and + + # Only return the property if it is a DSC property. $InputObject.GetType().GetMember($_).CustomAttributes.Where( { $_.AttributeType.Name -eq 'DscPropertyAttribute' @@ -47,11 +54,11 @@ function Get-DscProperty ) } - # Filter out the specified name. - if ($PSBoundParameters.ContainsKey('Name')) - { - $property = @($property) -eq $Name - } + # # Filter out the specified name. + # if ($PSBoundParameters.ContainsKey('Name')) + # { + # $property = @($property) -eq $Name + # } if (-not [System.String]::IsNullOrEmpty($property)) { diff --git a/source/en-US/SqlDatabasePermission.strings.psd1 b/source/en-US/SqlDatabasePermission.strings.psd1 index f7335f2f8..03ace852a 100644 --- a/source/en-US/SqlDatabasePermission.strings.psd1 +++ b/source/en-US/SqlDatabasePermission.strings.psd1 @@ -15,7 +15,7 @@ ConvertFrom-StringData @' DesiredPermissionAreAbsent = The desired permission '{0}' that shall be present are absent. (SDP0003) FailedToRevokePermissionFromCurrentState = Failed to revoke the permissions from the current state for the user '{0}' in the database '{1}'. (SDP0007) FailedToSetPermission = Failed to set the desired permissions for the user '{0}' in the database '{1}'. (SDP0007) - RevokePermissionNotInDesiredState = Revoking permission '{0}' for the user '{1}' in the database '{2}' since the permission is not part of the desired state. (SDP0008) DuplicatePermissionState = One or more permission states was added more than once. It is only allowed to specify one of each permission state. (SDP0009) MissingPermissionState = One or more permission states was missing. One of each permission state must be provided. (SDP0009) + MustAssignOnePermissionProperty = At least one of the properties 'Permission', 'PermissionToInclude', or 'PermissionToExclude' must be specified. (SDP0010) '@ diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index 0e591956b..b8e2e927c 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -100,6 +100,14 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { State = 'Grant' Permission = @('Connect') } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } ) } @@ -118,6 +126,14 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { State = 'Grant' Permission = @('Connect') } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } ) } } @@ -160,6 +176,14 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { State = 'Grant' Permission = @('Connect') } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } ) } @@ -179,6 +203,14 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { State = 'Grant' Permission = @('Connect') } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } ) } } @@ -257,7 +289,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { } Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { - Context 'When there are no permission' { + Context 'When there are no permission in the current state' { BeforeAll { InModuleScope -ScriptBlock { $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ @@ -274,7 +306,7 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { Mock -CommandName Get-SqlDscDatabasePermission } - It 'Should return the state as present' { + It 'Should return empty collections for each state' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ Name = 'MockUserName' @@ -285,15 +317,30 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { $currentState.Credential | Should -BeNullOrEmpty $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' - $currentState.Permission | Should -BeNullOrEmpty + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -BeNullOrEmpty + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty - # $currentState.Permission[0].State | Should -Be 'Grant' - # $currentState.Permission[0].Permission | Should -Be 'Connect' + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty } } Context 'When using property Credential' { - It 'Should return the state as present' { + It 'Should return empty collections for each state' { InModuleScope -ScriptBlock { $script:mockSqlDatabasePermissionInstance.Credential = [System.Management.Automation.PSCredential]::new( 'MyCredentialUserName', @@ -311,7 +358,25 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { $currentState.Credential.UserName | Should -Be 'MyCredentialUserName' $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' - $currentState.Permission | Should -BeNullOrEmpty + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -BeNullOrEmpty + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty } } } @@ -354,7 +419,7 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { } } - It 'Should return the state as present' { + It 'Should return correct values for state Grant and empty collections for the two other states' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ Name = 'MockUserName' @@ -365,11 +430,26 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { $currentState.Credential | Should -BeNullOrEmpty $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' - $currentState.Permission | Should -HaveCount 1 + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty - $currentState.Permission[0].State | Should -Be 'Grant' - $currentState.Permission[0].Permission | Should -Contain 'Connect' - $currentState.Permission[0].Permission | Should -Contain 'Update' + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty } } } @@ -419,7 +499,7 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { } } - It 'Should return the state as present' { + It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ Name = 'MockUserName' @@ -430,145 +510,26 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { $currentState.Credential | Should -BeNullOrEmpty $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' - $currentState.Permission | Should -HaveCount 2 - - $currentState.Permission[0].State | Should -Be 'Grant' - $currentState.Permission[0].Permission | Should -Contain 'Connect' - $currentState.Permission[0].Permission | Should -Contain 'Update' - - $currentState.Permission[1].State | Should -Be 'Deny' - $currentState.Permission[1].Permission | Should -Contain 'Select' - } - } - } - - Context 'When a permission should not absent for state Grant' { - Context 'When the system is in the desired state' { - BeforeAll { - InModuleScope -ScriptBlock { - $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ - #Ensure = [Ensure]::Absent - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - Permission = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('Select') - } - ) - } - } - - Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { - return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' - } - - Mock -CommandName Get-SqlDscDatabasePermission -MockWith { - $mockDatabasePermissionInfo = @() - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - return $mockDatabasePermissionInfo - } - } - - It 'Should return the state as absent and return the correct current state' { - InModuleScope -ScriptBlock { - $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - }) - - #$currentState.Ensure | Should -Be ([Ensure]::Absent) - $currentState.Credential | Should -BeNullOrEmpty - - $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' - $currentState.Permission | Should -HaveCount 1 - - $currentState.Permission[0].State | Should -Be 'Grant' - $currentState.Permission[0].Permission | Should -Contain 'Connect' - $currentState.Permission[0].Permission | Should -Contain 'Update' - } - } - } - - Context 'When the system is not in the desired state' { - BeforeAll { - InModuleScope -ScriptBlock { - $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ - #Ensure = [Ensure]::Absent - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - Permission = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('Update') - } - ) - } - } + $currentState.Permission | Should -HaveCount 3 - Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { - return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' - } + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) - Mock -CommandName Get-SqlDscDatabasePermission -MockWith { - $mockDatabasePermissionInfo = @() - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - return $mockDatabasePermissionInfo - } - } + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' - It 'Should return the state as present and return the correct current state' { - InModuleScope -ScriptBlock { - $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - }) + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) - $currentState.Credential | Should -BeNullOrEmpty + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty - $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' - $currentState.Permission | Should -HaveCount 1 + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) - $currentState.Permission[0].State | Should -Be 'Grant' - $currentState.Permission[0].Permission | Should -Contain 'Connect' - $currentState.Permission[0].Permission | Should -Contain 'Update' - } + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -Contain 'Select' } } } diff --git a/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 b/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 index 78349ab32..663a47416 100644 --- a/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 +++ b/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 @@ -50,8 +50,6 @@ Describe 'Get-DesiredStateProperty' -Tag 'Private' { PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] diff --git a/tests/Unit/Private/Get-DscProperty.Tests.ps1 b/tests/Unit/Private/Get-DscProperty.Tests.ps1 index 06311252c..f4bd4539e 100644 --- a/tests/Unit/Private/Get-DscProperty.Tests.ps1 +++ b/tests/Unit/Private/Get-DscProperty.Tests.ps1 @@ -43,7 +43,8 @@ AfterAll { } Describe 'Get-DscProperty' -Tag 'Private' { - Context 'When getting all DSC properties' { + + Context 'When no property is returned' { BeforeAll { <# Must use a here-string because we need to pass 'using' which must be @@ -51,8 +52,36 @@ Describe 'Get-DscProperty' -Tag 'Private' { PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc +class MyMockResource +{ + [System.String] + $MyResourceKeyProperty1 +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -InputObject $script:mockResourceBaseInstance + + $result | Should -BeNullOrEmpty + } + } + } + Context 'When getting all DSC properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' class MyMockResource { [DscProperty(Key)] @@ -74,6 +103,10 @@ class MyMockResource [DscProperty(NotConfigurable)] [System.String] $MyResourceReadProperty + + [System.String] $ClassProperty + + hidden [System.String] $HiddenClassProperty } $script:mockResourceBaseInstance = [MyMockResource]::new() @@ -99,6 +132,9 @@ $script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' $result.Keys | Should -Contain 'MyResourceProperty' $result.Keys | Should -Contain 'MyResourceReadProperty' + $result.Keys | Should -Not -Contain 'ClassProperty' -Because 'the property is not a DSC property' + $result.Keys | Should -Not -Contain 'HiddenClassProperty' -Because 'the property is not a DSC property' + $result.MyResourceKeyProperty1 | Should -Be 'MockValue1' $result.MyResourceKeyProperty2 | Should -Be 'MockValue2' $result.MyResourceMandatoryProperty | Should -Be 'MockValue3' @@ -108,6 +144,128 @@ $script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' } } + Context 'When using parameter Name' { + Context 'When getting a single property' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty + + [System.String] $ClassProperty + + hidden [System.String] $HiddenClassProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Name 'MyResourceProperty' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -HaveCount 1 + $result.Keys | Should -Contain 'MyResourceProperty' + + $result.MyResourceProperty | Should -Be 'MockValue4' + } + } + } + + Context 'When getting multiple properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty + + [System.String] $ClassProperty + + hidden [System.String] $HiddenClassProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Name @('MyResourceProperty', 'MyResourceMandatoryProperty') -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'MyResourceProperty' + $result.Keys | Should -Contain 'MyResourceMandatoryProperty' + + $result.MyResourceProperty | Should -Be 'MockValue4' + $result.MyResourceMandatoryProperty | Should -Be 'MockValue3' + } + } + } + } + Context 'When using parameter Type' { Context 'When getting all key properties' { BeforeAll { @@ -117,8 +275,6 @@ $script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] @@ -179,8 +335,6 @@ $script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] @@ -240,8 +394,6 @@ $script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] @@ -301,8 +453,6 @@ $script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] @@ -362,8 +512,6 @@ $script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] @@ -416,6 +564,66 @@ $script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' } } + Context 'When getting specific property names of a certain type' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty + + [System.String] $ClassProperty + + hidden [System.String] $HiddenClassProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Name @('MyResourceProperty', 'MyResourceMandatoryProperty') -Type 'Mandatory' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -HaveCount 1 + $result.Keys | Should -Contain 'MyResourceMandatoryProperty' + + $result.Keys | Should -Not -Contain 'MyResourceProperty' -Because 'it is not a mandatory property' + + $result.MyResourceMandatoryProperty | Should -Be 'MockValue3' + } + } + } } Context 'When using parameter HasValue' { @@ -427,8 +635,6 @@ $script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] @@ -475,7 +681,7 @@ $script:mockResourceBaseInstance.MyResourceProperty2 = 'MockValue5' $result.Keys | Should -Not -Contain 'MyResourceMandatoryProperty' -Because 'mandatory properties should not be part of the collection' $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'key properties should not be part of the collection' $result.Keys | Should -Not -Contain 'MyResourceKeyProperty2' -Because 'key properties should not be part of the collection' - $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'optional properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' $result.Keys | Should -Not -Contain 'MyResourceProperty1' -Because 'the property has a $null value' @@ -486,4 +692,64 @@ $script:mockResourceBaseInstance.MyResourceProperty2 = 'MockValue5' } } } + + Context 'When getting specific named properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty1 + +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty2 + +[DscProperty(Mandatory)] +[System.String] +$MyResourceMandatoryProperty + +[DscProperty()] +[System.String] +$MyResourceProperty1 + +[DscProperty()] +[System.String] +$MyResourceProperty2 + +[DscProperty(NotConfigurable)] +[System.String] +$MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty2 = 'MockValue5' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Name @('MyResourceProperty1', 'MyResourceProperty2') -HasValue -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceProperty1' -Because 'the property has a $null value' + + $result.Keys | Should -Contain 'MyResourceProperty2' -Because 'the property has a non-null value' + + $result.MyResourceProperty2 | Should -Be 'MockValue5' + } + } + } } diff --git a/tests/Unit/Private/Get-KeyProperty.Tests.ps1 b/tests/Unit/Private/Get-KeyProperty.Tests.ps1 index 16bc625cd..eec799e1b 100644 --- a/tests/Unit/Private/Get-KeyProperty.Tests.ps1 +++ b/tests/Unit/Private/Get-KeyProperty.Tests.ps1 @@ -50,8 +50,6 @@ Describe 'Get-KeyProperty' -Tag 'Private' { PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] diff --git a/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 b/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 index f0b6b2198..3a4e95430 100644 --- a/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 +++ b/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 @@ -51,8 +51,6 @@ Describe 'Test-ResourceHasProperty' -Tag 'Private' { PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] @@ -95,8 +93,6 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] @@ -139,8 +135,6 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] @@ -183,8 +177,6 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] @@ -230,8 +222,6 @@ $script:mockResourceBaseInstance = [MyMockResource] @{ PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] diff --git a/tests/Unit/Private/Test-ResourcePropertyIsAssigned.Tests.ps1 b/tests/Unit/Private/Test-ResourcePropertyIsAssigned.Tests.ps1 index f58396dba..bf4659760 100644 --- a/tests/Unit/Private/Test-ResourcePropertyIsAssigned.Tests.ps1 +++ b/tests/Unit/Private/Test-ResourcePropertyIsAssigned.Tests.ps1 @@ -51,8 +51,6 @@ Describe 'Test-ResourcePropertyIsAssigned' -Tag 'Private' { PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] @@ -98,8 +96,6 @@ $script:mockResourceBaseInstance = [MyMockResource] @{ PowerShell will fail to parse the test script. #> $inModuleScopeScriptBlock = @' -using module SqlServerDsc - class MyMockResource { [DscProperty(Key)] From f4176af038e00964dc9b1200e0e196f8e7ff18ff Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 20 Jul 2022 16:43:11 +0200 Subject: [PATCH 49/98] Fix unit tests --- .../Classes/SqlDatabasePermission.Tests.ps1 | 356 ++++++++++++++++++ 1 file changed, 356 insertions(+) diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index b8e2e927c..836eee232 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -533,6 +533,362 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { } } } + + Context 'When using parameter PermissionToInclude' { + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToInclude = [DatabasePermission] @{ + State = 'Grant' + Permission = 'update' + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + $mockDatabasePermissionInfo = @() + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $false, $true, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Deny' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + return $mockDatabasePermissionInfo + } + } + + It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -Contain 'Select' + + $currentState.PermissionToInclude | Should -HaveCount 1 + $currentState.PermissionToInclude[0].State | Should -Be 'Grant' + $currentState.PermissionToInclude[0].Permission | Should -Be 'Update' + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToInclude = [DatabasePermission] @{ + State = 'Grant' + Permission = 'alter' + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + $mockDatabasePermissionInfo = @() + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $false, $true, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Deny' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + return $mockDatabasePermissionInfo + } + } + + It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -Contain 'Select' + + $currentState.PermissionToInclude | Should -HaveCount 1 + $currentState.PermissionToInclude[0].State | Should -Be 'Grant' + $currentState.PermissionToInclude[0].Permission | Should -BeNullOrEmpty + } + } + } + } + + Context 'When using parameter PermissionToExclude' { + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToExclude = [DatabasePermission] @{ + State = 'Grant' + Permission = 'alter' + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + $mockDatabasePermissionInfo = @() + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $false, $true, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Deny' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + return $mockDatabasePermissionInfo + } + } + + It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -Contain 'Select' + + $currentState.PermissionToExclude | Should -HaveCount 1 + $currentState.PermissionToExclude[0].State | Should -Be 'Grant' + $currentState.PermissionToExclude[0].Permission | Should -Be 'Alter' + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToExclude = [DatabasePermission] @{ + State = 'Grant' + Permission = 'update' + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + $mockDatabasePermissionInfo = @() + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + $mockDatabasePermissionInfo += New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $false, $true, $false)) -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Deny' -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + + return $mockDatabasePermissionInfo + } + } + + It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -Contain 'Select' + + $currentState.PermissionToExclude | Should -HaveCount 1 + $currentState.PermissionToExclude[0].State | Should -Be 'Grant' + $currentState.PermissionToExclude[0].Permission | Should -BeNullOrEmpty + } + } + } + } } Describe 'SqlDatabasePermission\Set()' -Tag 'Set' { From 8fff2bc186dee9c5baab208f35846fe21f8ea3d9 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 21 Jul 2022 13:59:27 +0200 Subject: [PATCH 50/98] Fix unit tests --- source/Classes/010.ResourceBase.ps1 | 6 +- source/Classes/020.SqlDatabasePermission.ps1 | 30 +- source/Private/Get-DscProperty.ps1 | 6 - .../Classes/SqlDatabasePermission.Tests.ps1 | 815 +++++++++++++++++- tests/Unit/Stubs/SMO.cs | 3 + 5 files changed, 822 insertions(+), 38 deletions(-) diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index a8e96340a..cb758c022 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -256,6 +256,7 @@ class ResourceBase #> hidden [System.Collections.Hashtable[]] Compare() { + # TODO: Replace ConvertFrom-DscResourceInstance with Get-DscProperty? $currentState = $this.Get() | ConvertFrom-DscResourceInstance return $this.Compare($currentState, @()) @@ -270,6 +271,7 @@ class ResourceBase #> hidden [System.Collections.Hashtable[]] Compare([System.Collections.Hashtable] $currentState, [System.String[]] $excludeProperties) { + # TODO: Replace this with Get-DscProperty $desiredState = $this | Get-DesiredStateProperty $CompareDscParameterState = @{ @@ -292,6 +294,8 @@ class ResourceBase # This method should normally not be overridden. hidden [void] Assert() { + # TODO: Replace this with Get-DscProperty + # Get the properties that has a non-null value and is not of type Read. $desiredState = $this | Get-DesiredStateProperty $this.AssertProperties($desiredState) @@ -300,7 +304,7 @@ class ResourceBase <# This method can be overridden if resource specific property asserts are needed. The parameter properties will contain the properties that was - passed a value. + assigned a value. #> [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidEmptyNamedBlocks', '')] hidden [void] AssertProperties([System.Collections.Hashtable] $properties) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 4282861e7..5183a8caf 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -418,15 +418,12 @@ class SqlDatabasePermission : ResourceBase <# Base method Set() call this method with the properties that should be - enforced and that are not in desired state. It is not called if all - properties are in desired state. The variable $properties contain the - properties that are not in desired state. + enforced are not in desired state. It is not called if all properties + are in desired state. The variable $properties contain the properties + that are not in desired state. #> hidden [void] Modify([System.Collections.Hashtable] $properties) { - # TODO: Remove line below - Write-Verbose -Message ($properties | Out-String) -Verbose - $connectSqlDscDatabaseEngineParameters = @{ ServerName = $this.ServerName InstanceName = $this.InstanceName @@ -474,6 +471,7 @@ class SqlDatabasePermission : ResourceBase Evaluate if there are any permissions that should be revoked from the current state. #> + # TODO: replace all $this.Permission* with $properties.Permission* foreach ($currentDesiredPermissionState in $this.Permission) { $currentPermissionsForState = $currentState.Permission | @@ -634,7 +632,7 @@ class SqlDatabasePermission : ResourceBase } <# - Base method Assert() call this method with the properties that was passed + Base method Assert() call this method with the properties that was assigned a value. #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) @@ -645,7 +643,7 @@ class SqlDatabasePermission : ResourceBase # PermissionToInclude and PermissionToExclude should be mutually exclusive from Permission $assertBoundParameterParameters = @{ - BoundParameterList = $this | Get-DscProperty -Type 'Optional' -HasValue + BoundParameterList = $properties MutuallyExclusiveList1 = @( 'Permission' ) @@ -658,16 +656,16 @@ class SqlDatabasePermission : ResourceBase Assert-BoundParameter @assertBoundParameterParameters # Get all assigned permission properties. - $assignedPermissionProperty = @( - $this | Get-DscProperty -HasValue -Name @( + $assignedPermissionProperty = $properties.Keys.Where({ + $_ -in @( 'Permission', 'PermissionToInclude', 'PermissionToExclude' ) - ) + }) # Must include either of the permission properties. - if (-not $assignedPermissionProperty) + if ([System.String]::IsNullOrEmpty($assignedPermissionProperty)) { # TODO: Should throw ArgumentException throw $this.localizedData.MustAssignOnePermissionProperty @@ -677,7 +675,7 @@ class SqlDatabasePermission : ResourceBase foreach ($currentAssignedPermissionProperty in $assignedPermissionProperty) { $permissionStateGroupCount = @( - $this.$currentAssignedPermissionProperty | + $properties.$currentAssignedPermissionProperty | Group-Object -NoElement -Property 'State' -CaseSensitive:$false | Select-Object -ExpandProperty 'Count' ) @@ -693,9 +691,9 @@ class SqlDatabasePermission : ResourceBase { # Each State must exist once. $missingPermissionState = ( - $this.Permission.State -notcontains 'Grant' -or - $this.Permission.State -notcontains 'GrantWithGrant' -or - $this.Permission.State -notcontains 'Deny' + $properties.Permission.State -notcontains 'Grant' -or + $properties.Permission.State -notcontains 'GrantWithGrant' -or + $properties.Permission.State -notcontains 'Deny' ) if ($missingPermissionState) diff --git a/source/Private/Get-DscProperty.ps1 b/source/Private/Get-DscProperty.ps1 index 5fc1db2ef..bc0ecb124 100644 --- a/source/Private/Get-DscProperty.ps1 +++ b/source/Private/Get-DscProperty.ps1 @@ -54,12 +54,6 @@ function Get-DscProperty ) } - # # Filter out the specified name. - # if ($PSBoundParameters.ContainsKey('Name')) - # { - # $property = @($property) -eq $Name - # } - if (-not [System.String]::IsNullOrEmpty($property)) { if ($PSBoundParameters.ContainsKey('Type')) diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index 836eee232..ddc26902a 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -88,7 +88,7 @@ Describe 'SqlDatabasePermission' { Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { Context 'When the system is in the desired state' { - Context 'When the desired permission should exist' { + Context 'When the desired permission exist' { BeforeAll { InModuleScope -ScriptBlock { $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ @@ -140,11 +140,10 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { } } - It 'Should return the state as present' { + It 'Should return the correct values' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.Get() - #$currentState.Ensure | Should -Be ([Ensure]::Present) $currentState.InstanceName | Should -Be 'NamedInstance' $currentState.DatabaseName | Should -Be 'MockDatabaseName' $currentState.Name | Should -Be 'MockUserName' @@ -160,7 +159,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { } } - Context 'When the desired permission should exist and using parameter Credential' { + Context 'When the desired permission exist and using parameter Credential' { BeforeAll { InModuleScope -ScriptBlock { $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ @@ -217,16 +216,14 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { } } - It 'Should return the state as present' { + It 'Should return the correct values' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.Get() - #$currentState.Ensure | Should -Be ([Ensure]::Present) $currentState.InstanceName | Should -Be 'NamedInstance' $currentState.DatabaseName | Should -Be 'MockDatabaseName' $currentState.Name | Should -Be 'MockUserName' $currentState.ServerName | Should -Be (Get-ComputerName) - $currentState.Reasons | Should -BeNullOrEmpty $currentState.Credential | Should -BeOfType [System.Management.Automation.PSCredential] @@ -236,19 +233,35 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { $currentState.Permission[0].State | Should -Be 'Grant' $currentState.Permission[0].Permission | Should -Be 'Connect' + + $currentState.Reasons | Should -HaveCount 0 } } } + } - Context 'When the desired permission should not exist' { + Context 'When the system is not in the desired state' { + Context 'When the desired permission exist' { BeforeAll { InModuleScope -ScriptBlock { $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ - #Ensure = [Ensure]::Absent Name = 'MockUserName' DatabaseName = 'MockDatabaseName' InstanceName = 'NamedInstance' - Permission = [DatabasePermission[]] @() + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) } <# @@ -261,27 +274,44 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { $script:mockSqlDatabasePermissionInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Permission = [DatabasePermission[]] @() + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect', 'Update') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) } } } } - It 'Should return the state as absent' { + It 'Should return the correct values' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.Get() - #$currentState.Ensure | Should -Be ([Ensure]::Absent) $currentState.InstanceName | Should -Be 'NamedInstance' $currentState.DatabaseName | Should -Be 'MockDatabaseName' $currentState.Name | Should -Be 'MockUserName' $currentState.ServerName | Should -Be (Get-ComputerName) $currentState.Credential | Should -BeNullOrEmpty - $currentState.Reasons | Should -BeNullOrEmpty $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' - $currentState.Permission | Should -BeNullOrEmpty + $currentState.Permission[0].State | Should -Be 'Grant' + $currentState.Permission[0].Permission | Should -Contain 'Connect' + $currentState.Permission[0].Permission | Should -Contain 'Update' + + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' + $currentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["Connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["Connect","Update"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' } } } @@ -898,6 +928,20 @@ Describe 'SqlDatabasePermission\Set()' -Tag 'Set' { Name = 'MockUserName' DatabaseName = 'MockDatabaseName' InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) } | # Mock method Modify which is called by the base method Set(). Add-Member -Force -MemberType 'ScriptMethod' -Name 'Modify' -Value { @@ -974,6 +1018,20 @@ Describe 'SqlDatabasePermission\Test()' -Tag 'Test' { Name = 'MockUserName' DatabaseName = 'MockDatabaseName' InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) } } } @@ -1028,3 +1086,730 @@ Describe 'SqlDatabasePermission\Test()' -Tag 'Test' { } } } + +Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { + Context 'When the database principal does not exist' { + BeforeAll { + InModuleScope -ScriptBlock { + # This test does not set a desired state as it is not necessary for this test. + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + # Credential is set to increase code coverage. + Credential = [System.Management.Automation.PSCredential]::new( + 'MyCredentialUserName', + [SecureString]::new() + ) + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $false + } + } + + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.NameIsMissing + } + + $mockErrorRecord = Get-InvalidOperationRecord -Message ( + $mockErrorMessage -f @( + 'MockUserName' + 'MockDatabaseName' + 'NamedInstance' + ) + ) + + InModuleScope -ScriptBlock { + { + # This test does not pass any properties to set as it is not necessary for this test. + $mockSqlDatabasePermissionInstance.Modify(@{ + Permission = [DatabasePermission[]] @() + }) + } | Should -Throw -ExpectedMessage $mockErrorRecord + } + } + } + + Context 'When property Permission is not in desired state' { + Context 'When a desired permissions is missing from the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + # This mocks the method GetCurrentState(). + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @() + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + Mock -CommandName Set-SqlDscDatabasePermission + } + + It 'Should call the correct mock with the correct parameter values' { + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.Modify(@{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) + } | Should -Not -Throw + } + + # Grants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Grant' -and $Permission.Connect -eq $true + } -Exactly -Times 1 -Scope It + + # GrantWithGrants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Grant' -and $Permission.Update -eq $true + } -Exactly -Times 1 -Scope It + } + } + + Context 'When a desired permission is missing from the current state and there are four permissions that should not exist in the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + # This mocks the method GetCurrentState(). + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Alter', 'Select') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Delete') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @('Create') + } + ) + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + Mock -CommandName Set-SqlDscDatabasePermission + } + + It 'Should call the correct mock with the correct parameter values' { + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.Modify(@{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) + } | Should -Not -Throw + } + + # Revoking Grants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Revoke' -and $Permission.Alter -eq $true -and $Permission.Select -eq $true + } -Exactly -Times 1 -Scope It + + # Revoking GrantWithGrants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Revoke' -and $Permission.Delete -eq $true + } -Exactly -Times 1 -Scope It + + # Revoking Denies + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Revoke' -and $Permission.Create -eq $true + } -Exactly -Times 1 -Scope It + + # Adding new Grant + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Grant' -and $Permission.Connect -eq $true + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When property PermissionToInclude is not in desired state' { + Context 'When a desired permissions is missing from the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToInclude = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + # This mocks the method GetCurrentState(). + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @() + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + Mock -CommandName Set-SqlDscDatabasePermission + } + + It 'Should call the correct mock with the correct parameter values' { + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.Modify(@{ + PermissionToInclude = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) + } | Should -Not -Throw + } + + # Grants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Grant' -and $Permission.Connect -eq $true + } -Exactly -Times 1 -Scope It + + # GrantWithGrants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Grant' -and $Permission.Update -eq $true + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When property PermissionToExclude is not in desired state' { + Context 'When a desired permissions is missing from the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToExclude = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + # This mocks the method GetCurrentState(). + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + Mock -CommandName Set-SqlDscDatabasePermission + } + + It 'Should call the correct mock with the correct parameter values' { + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.Modify(@{ + PermissionToExclude = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) + } | Should -Not -Throw + } + + # Revoking Grants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Revoke' -and $Permission.Connect -eq $true + } -Exactly -Times 1 -Scope It + + # Revoking GrantWithGrants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Revoke' -and $Permission.Update -eq $true + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When Set-SqlDscDatabasePermission fails to change permission' { + Context 'When granting permissions' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + # This mocks the method GetCurrentState(). + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @() + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + Mock -CommandName Set-SqlDscDatabasePermission -MockWith { + throw 'Mocked error' + } + } + + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.FailedToSetPermission + } + + $mockErrorRecord = Get-InvalidOperationRecord -Message ( + $mockErrorMessage -f @( + 'MockUserName' + 'MockDatabaseName' + ) + ) + + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.Modify(@{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) + } | Should -Throw -ExpectedMessage $mockErrorRecord + } + } + } + + Context 'When revoking permissions' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + # This mocks the method GetCurrentState(). + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + Mock -CommandName Set-SqlDscDatabasePermission -MockWith { + throw 'Mocked error' + } + } + + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.FailedToRevokePermissionFromCurrentState + } + + $mockErrorRecord = Get-InvalidOperationRecord -Message ( + $mockErrorMessage -f @( + 'MockUserName' + 'MockDatabaseName' + ) + ) + + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.Modify(@{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) + } | Should -Throw -ExpectedMessage $mockErrorRecord + } + } + } + } +} + +Describe 'SqlDatabasePermission\AssertProperties()' -Tag 'AssertProperties' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{} + } + } + + <# + These tests just check for the string localized ID. Since the error is part + of a command outside of SqlServerDsc, a small changes to the localized + string should not fail these tests. + #> + Context 'When passing mutually exclusive parameters' { + Context 'When passing Permission and PermissionToInclude' { + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{ + Permission = [DatabasePermission[]] @([DatabasePermission] @{}) + PermissionToInclude = [DatabasePermission[]] @([DatabasePermission] @{}) + }) + } | Should -Throw -ExpectedMessage '*DRC0010*' + } + } + } + + Context 'When passing Permission and PermissionToExclude' { + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{ + Permission = [DatabasePermission[]] @([DatabasePermission] @{}) + PermissionToExclude = [DatabasePermission[]] @([DatabasePermission] @{}) + }) + } | Should -Throw -ExpectedMessage '*DRC0010*' + } + } + } + } + + Context 'When not passing any permission property' { + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.MustAssignOnePermissionProperty + } + + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{}) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When a permission Property contain the same State twice' { + It 'Should throw the correct error for property ' -ForEach @( + @{ + MockPropertyName = 'Permission' + } + @{ + MockPropertyName = 'PermissionToInclude' + } + @{ + MockPropertyName = 'PermissionToExclude' + } + ) { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.DuplicatePermissionState + } + + InModuleScope -Parameters $_ -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{ + $MockPropertyName = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + } + [DatabasePermission] @{ + State = 'Grant' + } + ) + }) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When the property Permission is missing a state' { + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.MissingPermissionState + } + + InModuleScope -Parameters $_ -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{ + Permission = [DatabasePermission[]] @( + # Missing state Deny. + [DatabasePermission] @{ + State = 'Grant' + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + } + ) + }) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } +} diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index 71026324f..45b1f6154 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -223,6 +223,9 @@ public DatabasePermissionSet( bool connect, bool update, bool select, bool inser public bool Update = false; public bool Select = false; public bool Insert = false; + public bool Alter = false; + public bool Create = false; + public bool Delete = false; } // TypeName: Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo From 2af2835f3ff114613073dff2ca66e2519c27df62 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 22 Jul 2022 14:04:04 +0200 Subject: [PATCH 51/98] Fix/issue 1555 integration (#18) --- source/Classes/020.SqlDatabasePermission.ps1 | 28 +- ...qlDatabasePermission.Integration.Tests.ps1 | 212 +++++++++++-- .../DSC_SqlDatabasePermission.config.ps1 | 289 ++++++++++++------ 3 files changed, 392 insertions(+), 137 deletions(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 5183a8caf..d7d42e21b 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -253,10 +253,15 @@ class SqlDatabasePermission : ResourceBase $databasePermissionInfo | ForEach-Object -Process { # Convert from the type PermissionState to String. [System.String] $_.PermissionState - } | - Select-Object -Unique + } ) + <# + Single out unique permission in case it possible to receive the + same permission name twice. + #> + $permissionState = $permissionState | Select-Object -Unique + foreach ($currentPermissionState in $permissionState) { $filteredDatabasePermission = $databasePermissionInfo | @@ -266,17 +271,15 @@ class SqlDatabasePermission : ResourceBase $databasePermission = [DatabasePermission] @{ State = $currentPermissionState + Permission = [System.String[]] @() } - # Initialize variable permission - [System.String[]] $statePermissionResult = @() - foreach ($currentPermission in $filteredDatabasePermission) { - # get the permissions that is set to $true + # Get the permission names that is set to $true $permissionProperty = $currentPermission.PermissionType | Get-Member -MemberType 'Property' | - Select-Object -ExpandProperty 'Name' | + Select-Object -ExpandProperty 'Name' -Unique | Where-Object -FilterScript { $currentPermission.PermissionType.$_ } @@ -284,19 +287,10 @@ class SqlDatabasePermission : ResourceBase foreach ($currentPermissionProperty in $permissionProperty) { - $statePermissionResult += $currentPermissionProperty + $databasePermission.Permission += $currentPermissionProperty } } - <# - Sort and remove any duplicate permissions, also make sure - it is an array even if only one item. - #> - $databasePermission.Permission = @( - $statePermissionResult | - Sort-Object -Unique - ) - [DatabasePermission[]] $currentState.Permission += $databasePermission } } diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 021502e03..487405868 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -87,14 +87,19 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name - $resourceCurrentState.PermissionState | Should -Be 'Grant' - 'Select' | Should -BeIn $resourceCurrentState.Permissions - 'CreateTable' | Should -BeIn $resourceCurrentState.Permissions + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 3 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Select' + $grantState.Permission | Should -Contain 'CreateTable' } It 'Should return $true when Test-DscConfiguration is run' { @@ -148,14 +153,19 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Absent' $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name - $resourceCurrentState.PermissionState | Should -Be 'Grant' - 'Select' | Should -Not -BeIn $resourceCurrentState.Permissions - 'CreateTable' | Should -Not -BeIn $resourceCurrentState.Permissions + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Not -Contain 'Select' + $grantState.Permission | Should -Not -Contain 'CreateTable' } It 'Should return $true when Test-DscConfiguration is run' { @@ -209,14 +219,24 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name - $resourceCurrentState.PermissionState | Should -Be 'Deny' - 'Select' | Should -BeIn $resourceCurrentState.Permissions - 'CreateTable' | Should -BeIn $resourceCurrentState.Permissions + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 2 + $denyState.Permission | Should -Contain 'Select' + $denyState.Permission | Should -Contain 'CreateTable' } It 'Should return $true when Test-DscConfiguration is run' { @@ -270,14 +290,22 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Absent' $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name - $resourceCurrentState.PermissionState | Should -Be 'Deny' - 'Select' | Should -Not -BeIn $resourceCurrentState.Permissions - 'CreateTable' | Should -Not -BeIn $resourceCurrentState.Permissions + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 0 } It 'Should return $true when Test-DscConfiguration is run' { @@ -331,13 +359,18 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be 'guest' - $resourceCurrentState.PermissionState | Should -Be 'Grant' - 'Select' | Should -BeIn $resourceCurrentState.Permissions + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 2 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Select' } It 'Should return $true when Test-DscConfiguration is run' { @@ -391,13 +424,148 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Absent' $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be 'guest' - $resourceCurrentState.PermissionState | Should -Be 'Grant' - 'Select' | Should -Not -BeIn $resourceCurrentState.Permissions + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Not -Contain 'Select' + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_GrantPublic_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be 'public' + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 2 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Select' + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_RemoveGrantPublic_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be 'public' + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Not -Contain 'Select' } It 'Should return $true when Test-DscConfiguration is run' { diff --git a/tests/Integration/DSC_SqlDatabasePermission.config.ps1 b/tests/Integration/DSC_SqlDatabasePermission.config.ps1 index 0bde9e020..ef51ebb3b 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.config.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.config.ps1 @@ -19,7 +19,12 @@ else NodeName = 'localhost' CertificateFile = $env:DscPublicCertificatePath - UserName = "$env:COMPUTERNAME\SqlAdmin" + <# + This must be either the UPN username (e.g. username@domain.local) + or the user name without the NetBIOS name (e.g. username). Using + the NetBIOS name (e.g. DOMAIN\username) will not work. + #> + UserName = 'SqlAdmin' Password = 'P@ssw0rd1' ServerName = $env:COMPUTERNAME @@ -47,19 +52,33 @@ Configuration DSC_SqlDatabasePermission_Grant_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Present' - Name = $Node.User1_Name - DatabaseName = $Node.DatabaseName - PermissionState = 'Grant' - Permissions = @( - 'Select' - 'CreateTable' - ) - ServerName = $Node.ServerName InstanceName = $Node.InstanceName + DatabaseName = $Node.DatabaseName + Name = $Node.User1_Name + Permission = @( + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + 'Select' + 'CreateTable' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Deny' + Permission = @() + } + ) - PsDscRunAsCredential = New-Object ` + Credential = New-Object ` -TypeName System.Management.Automation.PSCredential ` -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } @@ -78,21 +97,28 @@ Configuration DSC_SqlDatabasePermission_RemoveGrant_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Absent' - Name = $Node.User1_Name + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Grant' - Permissions = @( - 'Select' - 'CreateTable' + Name = $Node.User1_Name + Permission = @( + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Deny' + Permission = @() + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } @@ -109,21 +135,31 @@ Configuration DSC_SqlDatabasePermission_Deny_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Present' - Name = $Node.User1_Name + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Deny' - Permissions = @( - 'Select' - 'CreateTable' + Name = $Node.User1_Name + Permission = @( + DatabasePermission + { + State = 'Deny' + Permission = @( + 'Select' + 'CreateTable' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + ) + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } @@ -140,21 +176,28 @@ Configuration DSC_SqlDatabasePermission_RemoveDeny_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Absent' - Name = $Node.User1_Name + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Deny' - Permissions = @( - 'Select' - 'CreateTable' + Name = $Node.User1_Name + Permission = @( + DatabasePermission + { + State = 'Deny' + Permission = @() + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + ) + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } @@ -174,20 +217,33 @@ Configuration DSC_SqlDatabasePermission_GrantGuest_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Present' - Name = 'guest' + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Grant' - Permissions = @( - 'Select' + Name = 'guest' + Permission = @( + <# + These are in the order Deny, Grant, and GrantWithGrant on purpose, + to verify the objects are sorted correctly by Compare(). + #> + DatabasePermission + { + State = 'Deny' + Permission = @() + } + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + 'Select' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } @@ -207,20 +263,32 @@ Configuration DSC_SqlDatabasePermission_RemoveGrantGuest_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Absent' - Name = 'guest' + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Grant' - Permissions = @( - 'Select' + Name = 'guest' + Permission = @( + <# + These are in the order Deny, Grant, and GrantWithGrant on purpose, + to verify the objects are sorted correctly by Compare(). + #> + DatabasePermission + { + State = 'Deny' + Permission = @() + } + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } @@ -240,20 +308,33 @@ Configuration DSC_SqlDatabasePermission_GrantPublic_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Present' - Name = 'public' + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Grant' - Permissions = @( - 'Select' + Name = 'public' + Permission = @( + <# + These are in the order Deny, Grant, and GrantWithGrant on purpose, + to verify the objects are sorted correctly by Compare(). + #> + DatabasePermission + { + State = 'Deny' + Permission = @() + } + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + 'Select' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } @@ -273,20 +354,32 @@ Configuration DSC_SqlDatabasePermission_RemoveGrantPublic_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Absent' - Name = 'public' + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Grant' - Permissions = @( - 'Select' + Name = 'public' + Permission = @( + <# + These are in the order Deny, Grant, and GrantWithGrant on purpose, + to verify the objects are sorted correctly by Compare(). + #> + DatabasePermission + { + State = 'Deny' + Permission = @() + } + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } From 64264f44e50f383282d68adb404a0a8162d38a3f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 22 Jul 2022 14:40:28 +0200 Subject: [PATCH 52/98] Fix integration test --- source/Classes/020.SqlDatabasePermission.ps1 | 13 ++++++------- .../DSC_SqlDatabasePermission.Integration.Tests.ps1 | 8 +++++++- tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 | 1 + 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index d7d42e21b..e9f77e618 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -249,18 +249,17 @@ class SqlDatabasePermission : ResourceBase # If permissions was returned, build the current permission array of [DatabasePermission]. if ($databasePermissionInfo) { + # TODO: Below code could be a command ConvertTo-DatabasePermission that returns an array of [DatabasePermission] + <# + Single out all unique states since every single permission can be + one object in the $databasePermissionInfo + #> $permissionState = @( $databasePermissionInfo | ForEach-Object -Process { # Convert from the type PermissionState to String. [System.String] $_.PermissionState } - ) - - <# - Single out unique permission in case it possible to receive the - same permission name twice. - #> - $permissionState = $permissionState | Select-Object -Unique + ) | Select-Object -Unique foreach ($currentPermissionState in $permissionState) { diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 487405868..bc8d8a73e 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -305,7 +305,13 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) $denyState.State | Should -Be 'Deny' - $denyState.Permission | Should -HaveCount 0 + <# + Using '-HaveCount 0' does not work as it returns the error + 'Expected an empty collection, but got collection with size 1 @($null).' + even though the array is empty (does not contain oen item that is $null). + Probably due to issue: https://github.com/pester/Pester/issues/1000 + #> + $denyState.Permission | Should -BeNullOrEmpty } It 'Should return $true when Test-DscConfiguration is run' { diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index ddc26902a..0f4992f9e 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -466,6 +466,7 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { $grantState | Should -Not -BeNullOrEmpty $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 2 $grantState.Permission | Should -Contain 'Connect' $grantState.Permission | Should -Contain 'Update' From ab79f8b561f11c3601f61bc3e7bb963b2e70f62f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 23 Jul 2022 00:10:47 +0200 Subject: [PATCH 53/98] Fix integration test (Invoke-DscResource) --- ...qlDatabasePermission.Integration.Tests.ps1 | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index bc8d8a73e..6d469a371 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -1,3 +1,7 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'because ConvertTo-SecureString is used to simplify the tests.')] +param () + BeforeDiscovery { try { @@ -578,4 +582,175 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', Test-DscConfiguration -Verbose | Should -Be 'True' } } + + Context 'When using Invoke-DscResource' { + BeforeAll { + $mockDefaultInvokeDscResourceParameters = @{ + ModuleName = $script:dscModuleName + Name = $script:dscResourceFriendlyName + Verbode = $true + } + + $mockSqlCredential = [System.Management.Automation.PSCredential]::new( + $ConfigurationData.AllNodes.UserName, + ($ConfigurationData.AllNodes.Password | ConvertTo-SecureString -AsPlainText -Force) + ) + + $mockDefaultInvokeDscResourceProperty = @{ + ServerName = $ConfigurationData.AllNodes.ServerName + InstanceName = $ConfigurationData.AllNodes.InstanceName + DatabaseName = $ConfigurationData.AllNodes.DatabaseName + Name = $ConfigurationData.AllNodes.User1_Name + Credential = $mockSqlCredential + } + + $mockDefaultNewCimInstanceParameters = @{ + ClientOnly = $true + Namespace = 'root/Microsoft/Windows/DesiredStateConfiguration' + ClassName = 'DatabasePermission' + } + } + + AfterEach { + Wait-ForIdleLcm + } + + Context 'When assigning parameter Permission' { + Context 'When the system is not in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect', + 'update', + 'alter' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = [System.String[]] @() + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = [System.String[]] @() + }) + ) + + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + # TODO: Make sure to test that this returns the correct output. + $grant.Reasons | Should -HaveCount 1 + } + + # It 'Should run method Test() and return the correct value' { + # Test-DscConfiguration -Verbose | Should -Be 'True' + # } + + # It 'Should run method Set() without throwing' { + # { + # $configurationParameters = @{ + # OutputPath = $TestDrive + # # The variable $ConfigurationData was dot-sourced above. + # ConfigurationData = $ConfigurationData + # } + + # & $configurationName @configurationParameters + + # $startDscConfigurationParameters = @{ + # Path = $TestDrive + # ComputerName = 'localhost' + # Wait = $true + # Verbose = $true + # Force = $true + # ErrorAction = 'Stop' + # } + + # Start-DscConfiguration @startDscConfigurationParameters + # } | Should -Not -Throw + # } + } + + Context 'When the system is in the desired state' { + # It 'Should run method Get() and return the correct values' { + # { + # $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + # } | Should -Not -Throw + + # $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + # $_.ConfigurationName -eq $configurationName ` + # -and $_.ResourceId -eq $resourceId + # } + + # $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + # $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + # $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + # $resourceCurrentState.Name | Should -Be 'public' + # $resourceCurrentState.Permission | Should -HaveCount 3 + + # $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + # $grantState.State | Should -Be 'Grant' + # $grantState.Permission | Should -HaveCount 1 + # $grantState.Permission | Should -Contain 'Connect' + # $grantState.Permission | Should -Not -Contain 'Select' + # } + + # It 'Should run method Test() and return the correct value' { + # Test-DscConfiguration -Verbose | Should -Be 'True' + # } + + # It 'Should run method Set() without throwing' { + # { + # $configurationParameters = @{ + # OutputPath = $TestDrive + # # The variable $ConfigurationData was dot-sourced above. + # ConfigurationData = $ConfigurationData + # } + + # & $configurationName @configurationParameters + + # $startDscConfigurationParameters = @{ + # Path = $TestDrive + # ComputerName = 'localhost' + # Wait = $true + # Verbose = $true + # Force = $true + # ErrorAction = 'Stop' + # } + + # Start-DscConfiguration @startDscConfigurationParameters + # } | Should -Not -Throw + # } + } + } + } } From 97f9a7097f54e83039ec35a1a381050dc6c4892c Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 23 Jul 2022 08:58:59 +0200 Subject: [PATCH 54/98] Fic integraton test --- .../DSC_SqlDatabasePermission.Integration.Tests.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 6d469a371..9ff5e6c85 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -588,7 +588,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $mockDefaultInvokeDscResourceParameters = @{ ModuleName = $script:dscModuleName Name = $script:dscResourceFriendlyName - Verbode = $true + Verbose = $true } $mockSqlCredential = [System.Management.Automation.PSCredential]::new( @@ -669,6 +669,8 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', # TODO: Make sure to test that this returns the correct output. $grant.Reasons | Should -HaveCount 1 + Write-Verbose -Verbose -Message ($grant.Reasons[0].Code | Out-String) + Write-Verbose -Verbose -Message ($grant.Reasons[0].Phrase | Out-String) } # It 'Should run method Test() and return the correct value' { From be5a636bdd0e8edf4677359dd0ad0efa52c5b506 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 23 Jul 2022 09:25:33 +0200 Subject: [PATCH 55/98] Fix integration tests --- source/Classes/020.SqlDatabasePermission.ps1 | 41 ++++++++++++++++--- ...qlDatabasePermission.Integration.Tests.ps1 | 2 + 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index e9f77e618..630b8a40f 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -29,6 +29,20 @@ resources that using advanced type like the parameter `Permission` does. Use the parameter `Credential` instead of `PSDscRunAsCredential`. + ### 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'. The format 'DOMAIN\username' + will not work. + + See more information in [Credential Overview](https://github.com/dsccommunity/SqlServerDsc/wiki/CredentialOverview). + ### Invalid values during compilation The parameter Permission is of type `[DatabasePermission]`. If a property @@ -59,7 +73,28 @@ current computer name. .PARAMETER Permission - An array of database permissions to enforce. + An array of database permissions to enforce. Any permission that is not + part of the desired state will be revoked. + + Must provide all permission states (`Grant`, `Deny`, `GrantWithGrant`) with + at least an empty string array for the class `DatabasePermission`'s property + `Permission`. + + This is an array of CIM instances of class `DatabasePermission` from the + namespace `root/Microsoft/Windows/DesiredStateConfiguration`. + + .PARAMETER PermissionToInclude + An array of database permissions to include to the current state. The + current state will not be affected unless the current state contradict the + desired state. For example if the desired state specifies a deny permissions + but in the current state that permission is granted, that permission will + be changed to be denied. + + This is an array of CIM instances of class `DatabasePermission` from the + namespace `root/Microsoft/Windows/DesiredStateConfiguration`. + + .PARAMETER PermissionToExclude + An array of database permissions to exclude (revoke) from the current state. This is an array of CIM instances of class `DatabasePermission` from the namespace `root/Microsoft/Windows/DesiredStateConfiguration`. @@ -156,10 +191,6 @@ class SqlDatabasePermission : ResourceBase [PSCredential] $Credential - # [DscProperty()] - # [Ensure] - # $Ensure = [Ensure]::Present - [DscProperty(NotConfigurable)] [Reason[]] $Reasons diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 9ff5e6c85..6c90f1a9a 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -653,6 +653,8 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) $grantState.State | Should -Be 'Grant' From cc1f4e0e91dbb68d1182e78bbee479c1533d370a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 23 Jul 2022 10:54:16 +0200 Subject: [PATCH 56/98] Fix integration test --- .../DSC_SqlDatabasePermission.Integration.Tests.ps1 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 6c90f1a9a..9a7fc82d4 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -669,10 +669,9 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $denyState.State | Should -Be 'Deny' $denyState.Permission | Should -BeNullOrEmpty - # TODO: Make sure to test that this returns the correct output. - $grant.Reasons | Should -HaveCount 1 - Write-Verbose -Verbose -Message ($grant.Reasons[0].Code | Out-String) - Write-Verbose -Verbose -Message ($grant.Reasons[0].Phrase | Out-String) + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' + Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) } # It 'Should run method Test() and return the correct value' { From 3c6fd60ad209410708c2c2b9dfaa1a44a45ec25b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 23 Jul 2022 12:06:27 +0200 Subject: [PATCH 57/98] Fix integration tests --- ...qlDatabasePermission.Integration.Tests.ps1 | 269 +++++++++++------- .../DSC_SqlDatabasePermission.config.ps1 | 12 +- 2 files changed, 172 insertions(+), 109 deletions(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 9a7fc82d4..6f3d78eba 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -583,8 +583,19 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } } + <# + These tests assumes that only permission left for test user 'User1' is + a grant for permission 'Connect'. + #> Context 'When using Invoke-DscResource' { BeforeAll { + <# + Clear any configuration that was applied by a previous test so that + the configuration is not enforced by LCM while the tests using + Invoke-DscResource are run. + #> + Clear-DscLcmConfiguration + $mockDefaultInvokeDscResourceParameters = @{ ModuleName = $script:dscModuleName Name = $script:dscResourceFriendlyName @@ -616,9 +627,9 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } Context 'When assigning parameter Permission' { - Context 'When the system is not in the desired state' { - It 'Should run method Get() and return the correct values' { - { + Context 'When only specifying permissions for state Grant' { + Context 'When the system is not in the desired state' { + BeforeAll { $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( @@ -639,120 +650,166 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', Permission = [System.String[]] @() }) ) + } + + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' + $resourceCurrentState.Reasons[0].Code | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect","update","alter"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["Connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + + # TODO: Remove this + Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) + } - $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + It 'Should run method Test() and return the state as $false' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() - $mockInvokeDscResourceParameters.Method = 'Get' - $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty - $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters - } | Should -Not -Throw + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name - $resourceCurrentState.Permission | Should -HaveCount 3 - $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty - $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + $resourceCurrentState.InDesiredState | Should -BeFalse + } - $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) - $grantState.State | Should -Be 'Grant' - $grantState.Permission | Should -HaveCount 1 - $grantState.Permission | Should -Contain 'Connect' + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() - $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) - $grantWithGrantState.State | Should -Be 'GrantWithGrant' - $grantWithGrantState.Permission | Should -BeNullOrEmpty + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty - $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) - $denyState.State | Should -Be 'Deny' - $denyState.Permission | Should -BeNullOrEmpty + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw - $resourceCurrentState.Reasons | Should -HaveCount 1 - $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' - Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) + $resourceCurrentState.RebootRequired | Should -BeFalse + } } - # It 'Should run method Test() and return the correct value' { - # Test-DscConfiguration -Verbose | Should -Be 'True' - # } - - # It 'Should run method Set() without throwing' { - # { - # $configurationParameters = @{ - # OutputPath = $TestDrive - # # The variable $ConfigurationData was dot-sourced above. - # ConfigurationData = $ConfigurationData - # } - - # & $configurationName @configurationParameters - - # $startDscConfigurationParameters = @{ - # Path = $TestDrive - # ComputerName = 'localhost' - # Wait = $true - # Verbose = $true - # Force = $true - # ErrorAction = 'Stop' - # } - - # Start-DscConfiguration @startDscConfigurationParameters - # } | Should -Not -Throw - # } - } + Context 'When the system is not in the desired state' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() - Context 'When the system is in the desired state' { - # It 'Should run method Get() and return the correct values' { - # { - # $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - # } | Should -Not -Throw - - # $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - # $_.ConfigurationName -eq $configurationName ` - # -and $_.ResourceId -eq $resourceId - # } - - # $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - # $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - # $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName - # $resourceCurrentState.Name | Should -Be 'public' - # $resourceCurrentState.Permission | Should -HaveCount 3 - - # $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) - - # $grantState.State | Should -Be 'Grant' - # $grantState.Permission | Should -HaveCount 1 - # $grantState.Permission | Should -Contain 'Connect' - # $grantState.Permission | Should -Not -Contain 'Select' - # } - - # It 'Should run method Test() and return the correct value' { - # Test-DscConfiguration -Verbose | Should -Be 'True' - # } - - # It 'Should run method Set() without throwing' { - # { - # $configurationParameters = @{ - # OutputPath = $TestDrive - # # The variable $ConfigurationData was dot-sourced above. - # ConfigurationData = $ConfigurationData - # } - - # & $configurationName @configurationParameters - - # $startDscConfigurationParameters = @{ - # Path = $TestDrive - # ComputerName = 'localhost' - # Wait = $true - # Verbose = $true - # Force = $true - # ErrorAction = 'Stop' - # } - - # Start-DscConfiguration @startDscConfigurationParameters - # } | Should -Not -Throw - # } + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect', + 'update', + 'alter' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = [System.String[]] @() + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = [System.String[]] @() + }) + ) + } + + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + $resourceCurrentState.Reasons | Should -BeNullOrEmpty + } + + It 'Should run method Test() and return the state as $true' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeTrue + } + + # TODO: This test is meant to show that Set does not call + It 'Should run method Set() without throwing and not require reboot' { + Mock -CommandName Set-SqlDscDatabasePermission -ModuleName $script:dscModuleName -MockWith { + throw 'The mock of command Set-SqlDscDatabasePermission was called by a code path, but the command Set-SqlDscDatabasePermission should not have been called by the test.' + } + + { + # TODO: Remove this + $mockInvokeDscResourceProperty.Permission[0].Permission += 'create' + + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } } } } diff --git a/tests/Integration/DSC_SqlDatabasePermission.config.ps1 b/tests/Integration/DSC_SqlDatabasePermission.config.ps1 index ef51ebb3b..e9285c876 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.config.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.config.ps1 @@ -1,7 +1,13 @@ -#region HEADER -# Integration Test Config Template Version: 1.2.0 -#endregion +<# + .NOTES + There are integration tests in the file DSC_SqlDatabasePermission.Integration.Tests.ps1 + that is using the command Invoke-DscResource to run tests. Those test does + not have a configuration in this file, but do use the $ConfigurationData. + The tests using the command Invoke-DscResource assumes that only permission + left for test user 'User1' after running the configurations in this file is + a grant for permission 'Connect'. +#> $configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') if (Test-Path -Path $configFile) { From 73daab748e20a194da64b00a73f34a6231098bef Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 23 Jul 2022 13:28:08 +0200 Subject: [PATCH 58/98] Fix integration tests --- .../DSC_SqlDatabasePermission.Integration.Tests.ps1 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 6f3d78eba..e32c20893 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -685,7 +685,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' - $resourceCurrentState.Reasons[0].Code | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect","update","alter"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["Connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect","update","alter"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["Connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' # TODO: Remove this Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) @@ -762,8 +762,10 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) $grantState.State | Should -Be 'Grant' - $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -HaveCount 3 $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + $grantState.Permission | Should -Contain 'Alter' $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) $grantWithGrantState.State | Should -Be 'GrantWithGrant' @@ -793,7 +795,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', It 'Should run method Set() without throwing and not require reboot' { Mock -CommandName Set-SqlDscDatabasePermission -ModuleName $script:dscModuleName -MockWith { throw 'The mock of command Set-SqlDscDatabasePermission was called by a code path, but the command Set-SqlDscDatabasePermission should not have been called by the test.' - } + } -RemoveParameterType @('ServerObject', 'Permission') { # TODO: Remove this From 245b4ec9ba48ec8e7261085188a77c37e490bf14 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 23 Jul 2022 14:44:25 +0200 Subject: [PATCH 59/98] Fix integration tests --- ...qlDatabasePermission.Integration.Tests.ps1 | 26 ++++++++++++++++--- tests/Integration/DSC_SqlSetup.config.ps1 | 2 +- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index e32c20893..b9262769f 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -791,11 +791,29 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.InDesiredState | Should -BeTrue } + # TODO: This is a duplicate with the next test, if the next test works, this should be removed. + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + # TODO: This test is meant to show that Set does not call It 'Should run method Set() without throwing and not require reboot' { - Mock -CommandName Set-SqlDscDatabasePermission -ModuleName $script:dscModuleName -MockWith { - throw 'The mock of command Set-SqlDscDatabasePermission was called by a code path, but the command Set-SqlDscDatabasePermission should not have been called by the test.' - } -RemoveParameterType @('ServerObject', 'Permission') + # Import the module SqlServer to the complex types can be parsed by Mock below. + Import-Module -Name SqlServer + + Mock -CommandName Set-SqlDscDatabasePermission -ModuleName $script:dscModuleName + # -MockWith { + # throw 'The mock of command Set-SqlDscDatabasePermission was called by a code path, but the command Set-SqlDscDatabasePermission should not have been called by the test.' + # } { # TODO: Remove this @@ -810,6 +828,8 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } | Should -Not -Throw $resourceCurrentState.RebootRequired | Should -BeFalse + + Should -Not -Invoke -CommandName Set-SqlDscDatabasePermission -ModuleName $script:dscModuleName } } } diff --git a/tests/Integration/DSC_SqlSetup.config.ps1 b/tests/Integration/DSC_SqlSetup.config.ps1 index cc896ba8f..ea1849abc 100644 --- a/tests/Integration/DSC_SqlSetup.config.ps1 +++ b/tests/Integration/DSC_SqlSetup.config.ps1 @@ -275,7 +275,7 @@ Configuration DSC_SqlSetup_CreateDependencies_Config This module might already be installed on the build worker. This is needed to install SQL Server Analysis Services instances. - Thre SqlServer module is purposely not added to 'RequiredModule.psd1' so + The SqlServer module is purposely not added to 'RequiredModule.psd1' so that it does not conflict with the SqlServerStubs module that is used by unit tests. #> From ede6d7c490f6fcc2cd267575307344f84328528f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 23 Jul 2022 14:47:43 +0200 Subject: [PATCH 60/98] Fix integration test --- .../DSC_SqlDatabasePermission.Integration.Tests.ps1 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index b9262769f..01426a5db 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -742,6 +742,14 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', ) } + AfterAll { + <# + This will remove the module SqlServer from the session + after it was imported by a test below). + #> + Get-Module -Name 'SqlServer' -All | Remove-Module -Force + } + It 'Should run method Get() and return the correct values' { { $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() @@ -808,7 +816,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', # TODO: This test is meant to show that Set does not call It 'Should run method Set() without throwing and not require reboot' { # Import the module SqlServer to the complex types can be parsed by Mock below. - Import-Module -Name SqlServer + Import-Module -Name 'SqlServer' Mock -CommandName Set-SqlDscDatabasePermission -ModuleName $script:dscModuleName # -MockWith { From f520fe0ad0b4cee8f152d1cddeae6dd16b9da14b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 23 Jul 2022 16:00:30 +0200 Subject: [PATCH 61/98] Fix integration test --- .../DSC_SqlDatabasePermission.Integration.Tests.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 01426a5db..a5dde3e7e 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -818,6 +818,11 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', # Import the module SqlServer to the complex types can be parsed by Mock below. Import-Module -Name 'SqlServer' + <# + Mocking the command Set-SqlDscDatabasePermission to make + sure it is not called in this test, since this test tests + so that the configuration is already in desired state. + #> Mock -CommandName Set-SqlDscDatabasePermission -ModuleName $script:dscModuleName # -MockWith { # throw 'The mock of command Set-SqlDscDatabasePermission was called by a code path, but the command Set-SqlDscDatabasePermission should not have been called by the test.' @@ -825,7 +830,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', { # TODO: Remove this - $mockInvokeDscResourceProperty.Permission[0].Permission += 'create' + $mockInvokeDscResourceProperty.Permission[0].Permission += 'delete' $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() From b7aac639c11e46eaf104d9dabc84b741edb9e832 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 23 Jul 2022 17:03:42 +0200 Subject: [PATCH 62/98] Fix integration tests --- ...qlDatabasePermission.Integration.Tests.ps1 | 46 ++++++------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index a5dde3e7e..d7a47fcd6 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -799,7 +799,19 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.InDesiredState | Should -BeTrue } - # TODO: This is a duplicate with the next test, if the next test works, this should be removed. + <# + This test is meant to validate that method Set() also evaluates + the current state against the desired state, and if they match + the Set() method returns without calling Set-SqlDscDatabasePermission + to change permissions. + + It is not possible to validate that Set-SqlDscDatabasePermission + is not call since it is not possible to mock the command in + the session when LCM runs (which Invoke-DscResource invokes). + There are no other indications that can be caught to validate + this, unless looking for the verbose output that says that + all properties are in desired state. + #> It 'Should run method Set() without throwing and not require reboot' { { $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() @@ -812,38 +824,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.RebootRequired | Should -BeFalse } - - # TODO: This test is meant to show that Set does not call - It 'Should run method Set() without throwing and not require reboot' { - # Import the module SqlServer to the complex types can be parsed by Mock below. - Import-Module -Name 'SqlServer' - - <# - Mocking the command Set-SqlDscDatabasePermission to make - sure it is not called in this test, since this test tests - so that the configuration is already in desired state. - #> - Mock -CommandName Set-SqlDscDatabasePermission -ModuleName $script:dscModuleName - # -MockWith { - # throw 'The mock of command Set-SqlDscDatabasePermission was called by a code path, but the command Set-SqlDscDatabasePermission should not have been called by the test.' - # } - - { - # TODO: Remove this - $mockInvokeDscResourceProperty.Permission[0].Permission += 'delete' - - $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() - - $mockInvokeDscResourceParameters.Method = 'Set' - $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty - - $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters - } | Should -Not -Throw - - $resourceCurrentState.RebootRequired | Should -BeFalse - - Should -Not -Invoke -CommandName Set-SqlDscDatabasePermission -ModuleName $script:dscModuleName - } } } } From a1516e1b838672feeaf2eeb59a4cc0ececeb9c84 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 23 Jul 2022 17:04:23 +0200 Subject: [PATCH 63/98] Fix integration tests --- .../Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index d7a47fcd6..4e34f88a6 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -718,7 +718,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } } - Context 'When the system is not in the desired state' { + Context 'When the system is in the desired state' { BeforeAll { $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() From 2c8a1803b0092f44d72dd7fcfb497afc188e7b05 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 24 Jul 2022 09:05:44 +0200 Subject: [PATCH 64/98] Fic integration tests --- ...qlDatabasePermission.Integration.Tests.ps1 | 438 +++++++++++++++++- tests/Integration/README.md | 7 +- 2 files changed, 438 insertions(+), 7 deletions(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 4e34f88a6..c5efd08d3 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -626,7 +626,40 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', Wait-ForIdleLcm } + <# + The tests will leave Connect as granted permission. + + Test 1: Assigning the permissions Connect, Update, and Alter for the state + Grant. + Testing: Adding permission for state Grant, and testing to handle + the permission Connect that already exist from previous tests. + + Test 2: Assigning permission Delete and Update for the state Deny, and + permission Connect for the state Grant. + Testing: Adding permission for state Deny. From previous test + the permission Alter will be revoked, and the permission Update + will be moved from Grant to Deny. + + Test 3: Assigning permission Select for the state GrantWithGrant, and + permission Connect for the state Grant. + Testing: Adding permission for state GrantWithGrant. From previous + test the permission Delete and Update will be revoked. + + Test 4: Assigning permission Connect for the state Grant. + Testing: From previous test the permission Select will be revoked. + + TODO: MOVE THIS + Test 5: PermissionToInclude + + Test 6: PermissionToExclude + #> Context 'When assigning parameter Permission' { + <# + Test 1: Assigning the permissions Connect, Update, and Alter for the state + Grant. + Testing: Adding permission for state Grant, and testing to handle + the permission Connect that already exist from previous tests. + #> Context 'When only specifying permissions for state Grant' { Context 'When the system is not in the desired state' { BeforeAll { @@ -742,12 +775,114 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', ) } - AfterAll { - <# - This will remove the module SqlServer from the session - after it was imported by a test below). - #> - Get-Module -Name 'SqlServer' -All | Remove-Module -Force + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 3 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + $grantState.Permission | Should -Contain 'Alter' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + $resourceCurrentState.Reasons | Should -BeNullOrEmpty + } + + It 'Should run method Test() and return the state as $true' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeTrue + } + + <# + This test is meant to validate that method Set() also evaluates + the current state against the desired state, and if they match + the Set() method returns without calling Set-SqlDscDatabasePermission + to change permissions. + + It is not possible to validate that Set-SqlDscDatabasePermission + is not call since it is not possible to mock the command in + the session when LCM runs (which Invoke-DscResource invokes). + There are no other indications that can be caught to validate + this, unless looking for the verbose output that says that + all properties are in desired state. + #> + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + } + + <# + Test 2: Assigning permission Delete and Update for the state Deny, and + permission Connect for the state Grant. + Testing: Adding permission for state Deny. From previous test + the permission Alter will be revoked, and the permission Update + will be moved from Grant to Deny. + #> + Context 'When only specifying permissions for state Deny' { + Context 'When the system is not in the desired state' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = [System.String[]] @() + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = @( + 'delete', + 'update' + ) + }) + ) } It 'Should run method Get() and return the correct values' { @@ -783,6 +918,297 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $denyState.State | Should -Be 'Deny' $denyState.Permission | Should -BeNullOrEmpty + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}], but was [{"State":"Grant","Permission":["connect","update","alter"]}},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + + # TODO: Remove this + Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) + } + + It 'Should run method Test() and return the state as $false' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeFalse + } + + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect', + 'update', + 'alter' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = [System.String[]] @() + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = [System.String[]] @() + }) + ) + } + + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 2 + $denyState.Permission | Should -Contain 'Delete' + $denyState.Permission | Should -Contain 'Update' + + $resourceCurrentState.Reasons | Should -BeNullOrEmpty + } + + It 'Should run method Test() and return the state as $true' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeTrue + } + + <# + This test is meant to validate that method Set() also evaluates + the current state against the desired state, and if they match + the Set() method returns without calling Set-SqlDscDatabasePermission + to change permissions. + + It is not possible to validate that Set-SqlDscDatabasePermission + is not call since it is not possible to mock the command in + the session when LCM runs (which Invoke-DscResource invokes). + There are no other indications that can be caught to validate + this, unless looking for the verbose output that says that + all properties are in desired state. + #> + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + } + + <# + Test 3: Assigning permission Select for the state GrantWithGrant, and + permission Connect for the state Grant. + Testing: Adding permission for state GrantWithGrant. From previous + test the permission Delete and Update will be revoked. + #> + Context 'When only specifying permissions for state Deny' { + Context 'When the system is not in the desired state' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = @( + 'select' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = [System.String[]] @() + }) + ) + } + + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 2 + $denyState.Permission | Should -Contain 'Delete' + $denyState.Permission | Should -Contain 'Update' + + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]}},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}]' + + # TODO: Remove this + Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) + } + + It 'Should run method Test() and return the state as $false' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeFalse + } + + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect', + 'update', + 'alter' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = [System.String[]] @() + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = [System.String[]] @() + }) + ) + } + + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -HaveCount 1 + $grantWithGrantState.Permission | Should -Contain 'Select' + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + $resourceCurrentState.Reasons | Should -BeNullOrEmpty } diff --git a/tests/Integration/README.md b/tests/Integration/README.md index 611053925..8c53c3bbb 100644 --- a/tests/Integration/README.md +++ b/tests/Integration/README.md @@ -377,7 +377,12 @@ AsymmetricKey1 | RSA_2048 | P@ssw0rd1 **Depends on:** SqlDatabaseUser -The integration test will not leave anything on any instance. +The integration test will leave the following database permission on +principals. + +Principal | State | Permission +--- | --- | --- +User1 | Grant | Connect ## SqlWindowsFirewall From 093e007602ec3e29f0b7e45effe9e7ca2556f281 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 24 Jul 2022 10:17:51 +0200 Subject: [PATCH 65/98] Fix integration tests --- ...qlDatabasePermission.Integration.Tests.ps1 | 377 +++++++++++------- 1 file changed, 239 insertions(+), 138 deletions(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index c5efd08d3..165788c76 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -661,30 +661,30 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', the permission Connect that already exist from previous tests. #> Context 'When only specifying permissions for state Grant' { - Context 'When the system is not in the desired state' { - BeforeAll { - $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() - - $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'Grant' - Permission = @( - 'connect', - 'update', - 'alter' - ) - }) - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'GrantWithGrant' - Permission = [System.String[]] @() - }) - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'Deny' - Permission = [System.String[]] @() - }) - ) - } + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect', + 'update', + 'alter' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = [System.String[]] @() + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = [System.String[]] @() + }) + ) + } + Context 'When the system is not in the desired state' { It 'Should run method Get() and return the correct values' { { $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() @@ -752,29 +752,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } Context 'When the system is in the desired state' { - BeforeAll { - $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() - - $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'Grant' - Permission = @( - 'connect', - 'update', - 'alter' - ) - }) - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'GrantWithGrant' - Permission = [System.String[]] @() - }) - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'Deny' - Permission = [System.String[]] @() - }) - ) - } - It 'Should run method Get() and return the correct values' { { $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() @@ -860,31 +837,31 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', will be moved from Grant to Deny. #> Context 'When only specifying permissions for state Deny' { - Context 'When the system is not in the desired state' { - BeforeAll { - $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() - - $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'Grant' - Permission = @( - 'connect' - ) - }) - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'GrantWithGrant' - Permission = [System.String[]] @() - }) - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'Deny' - Permission = @( - 'delete', - 'update' - ) - }) - ) - } + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = [System.String[]] @() + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = @( + 'delete', + 'update' + ) + }) + ) + } + Context 'When the system is not in the desired state' { It 'Should run method Get() and return the correct values' { { $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() @@ -954,29 +931,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } Context 'When the system is in the desired state' { - BeforeAll { - $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() - - $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'Grant' - Permission = @( - 'connect', - 'update', - 'alter' - ) - }) - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'GrantWithGrant' - Permission = [System.String[]] @() - }) - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'Deny' - Permission = [System.String[]] @() - }) - ) - } - It 'Should run method Get() and return the correct values' { { $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() @@ -1060,31 +1014,31 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', Testing: Adding permission for state GrantWithGrant. From previous test the permission Delete and Update will be revoked. #> - Context 'When only specifying permissions for state Deny' { - Context 'When the system is not in the desired state' { - BeforeAll { - $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() - - $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'Grant' - Permission = @( - 'connect' - ) - }) - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'GrantWithGrant' - Permission = @( - 'select' - ) - }) - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'Deny' - Permission = [System.String[]] @() - }) - ) - } + Context 'When only specifying permissions for state GrantWithGrant' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = @( + 'select' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = [System.String[]] @() + }) + ) + } + Context 'When the system is not in the desired state' { It 'Should run method Get() and return the correct values' { { $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() @@ -1154,29 +1108,109 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } Context 'When the system is in the desired state' { - BeforeAll { - $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() - - $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'Grant' - Permission = @( - 'connect', - 'update', - 'alter' - ) - }) - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'GrantWithGrant' - Permission = [System.String[]] @() - }) - (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ - State = 'Deny' - Permission = [System.String[]] @() - }) - ) + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -HaveCount 1 + $grantWithGrantState.Permission | Should -Contain 'Select' + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + $resourceCurrentState.Reasons | Should -BeNullOrEmpty + } + + It 'Should run method Test() and return the state as $true' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeTrue + } + + <# + This test is meant to validate that method Set() also evaluates + the current state against the desired state, and if they match + the Set() method returns without calling Set-SqlDscDatabasePermission + to change permissions. + + It is not possible to validate that Set-SqlDscDatabasePermission + is not call since it is not possible to mock the command in + the session when LCM runs (which Invoke-DscResource invokes). + There are no other indications that can be caught to validate + this, unless looking for the verbose output that says that + all properties are in desired state. + #> + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse } + } + } + <# + Test 4: Assigning permission Connect for the state Grant. + Testing: From previous test the permission Select will be revoked. + #> + Context 'When only specifying permissions for state Grant to revoke permission in state GrantWithGrant' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = [System.String[]] @() + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = [System.String[]] @() + }) + ) + } + + Context 'When the system is not in the desired state' { It 'Should run method Get() and return the correct values' { { $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() @@ -1209,6 +1243,73 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $denyState.State | Should -Be 'Deny' $denyState.Permission | Should -BeNullOrEmpty + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]}},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}]' + + # TODO: Remove this + Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) + } + + It 'Should run method Test() and return the state as $false' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeFalse + } + + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + + Context 'When the system is in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + $resourceCurrentState.Reasons | Should -BeNullOrEmpty } From 0fdfed01320d3c4bcd308aad8b297d8542b7dde7 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 24 Jul 2022 10:25:24 +0200 Subject: [PATCH 66/98] Fix CHANGELOG.md --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ee9aac0d..b084d153a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,11 +129,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `Get-TargetResource`. The `Ensure` parameter now returns `Present` if Always On HADR is enabled and `Absent` if it is disabled. - SqlDatabasePermission - - BREAKING CHANGE: The parameter `ParameterState` has been removed and - the parameter `Permission` is now an instance of the type `DatabasePermission`. + - BREAKING CHANGE: The resource has been refactored. The parameters + `ParameterState` and `Permissions` has been replaced by parameters + `Permission`, `PermissionToInclude`, and `PermissionToExclude`. These + permissions parameters are now an instance of the type `DatabasePermission`. The type `DatabasePermission` contains two properties; `State` and `Permission`. - - Refactored resource as a class-based resource. + - The resource was refactored into a class-based resource. ### Fixed From 43cd0ee9e75e95bf7b879e927bf5aee152a34187 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 24 Jul 2022 11:00:56 +0200 Subject: [PATCH 67/98] Fix comment-based help and examples --- source/Classes/020.SqlDatabasePermission.ps1 | 24 +++++++----- ...sions.ps1 => 1-UseParameterPermission.ps1} | 0 .../1-UseParameterPermissionToExclude.ps1 | 38 +++++++++++++++++++ .../1-UseParameterPermissionToInclude.ps1 | 37 ++++++++++++++++++ 4 files changed, 89 insertions(+), 10 deletions(-) rename source/Examples/Resources/SqlDatabasePermission/{1-SetDatabasePermissions.ps1 => 1-UseParameterPermission.ps1} (100%) create mode 100644 source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToExclude.ps1 create mode 100644 source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToInclude.ps1 diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 630b8a40f..1f07920f8 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -12,8 +12,6 @@ >grantee and _all the other users the grantee has granted the same permission to_, >will also get their permission revoked. - Valid permission names can be found in the article [DatabasePermissionSet Class properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.databasepermissionset#properties). - ## Requirements * Target machine must be running Windows Server 2012 or later. @@ -77,11 +75,13 @@ part of the desired state will be revoked. Must provide all permission states (`Grant`, `Deny`, `GrantWithGrant`) with - at least an empty string array for the class `DatabasePermission`'s property - `Permission`. + at least an empty string array for the advanced type `DatabasePermission`'s + property `Permission`. + + Valid permission names can be found in the article [DatabasePermissionSet Class properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.databasepermissionset#properties). - This is an array of CIM instances of class `DatabasePermission` from the - namespace `root/Microsoft/Windows/DesiredStateConfiguration`. + This is an array of CIM instances of advanced type `DatabasePermission` from + the namespace `root/Microsoft/Windows/DesiredStateConfiguration`. .PARAMETER PermissionToInclude An array of database permissions to include to the current state. The @@ -90,14 +90,18 @@ but in the current state that permission is granted, that permission will be changed to be denied. - This is an array of CIM instances of class `DatabasePermission` from the - namespace `root/Microsoft/Windows/DesiredStateConfiguration`. + Valid permission names can be found in the article [DatabasePermissionSet Class properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.databasepermissionset#properties). + + This is an array of CIM instances of advanced type `DatabasePermission` from + the namespace `root/Microsoft/Windows/DesiredStateConfiguration`. .PARAMETER PermissionToExclude An array of database permissions to exclude (revoke) from the current state. - This is an array of CIM instances of class `DatabasePermission` from the - namespace `root/Microsoft/Windows/DesiredStateConfiguration`. + Valid permission names can be found in the article [DatabasePermissionSet Class properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.databasepermissionset#properties). + + This is an array of CIM instances of advanced type `DatabasePermission` from + the namespace `root/Microsoft/Windows/DesiredStateConfiguration`. .PARAMETER Credential Specifies the credential to use to connect to the _SQL Server_ instance. diff --git a/source/Examples/Resources/SqlDatabasePermission/1-SetDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermission.ps1 similarity index 100% rename from source/Examples/Resources/SqlDatabasePermission/1-SetDatabasePermissions.ps1 rename to source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermission.ps1 diff --git a/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToExclude.ps1 b/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToExclude.ps1 new file mode 100644 index 000000000..661abc103 --- /dev/null +++ b/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToExclude.ps1 @@ -0,0 +1,38 @@ +<# + .DESCRIPTION + This example shows how to enforce that if the user account CONTOSO\SQLAdmin + in the databases DB01 would be granted the permission "Delete" outside of + DSC, it is revoked. + Any other existing permissions in the states Grant, Deny, and GrantWithGrant + will not be changed. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost + { + SqlDatabasePermission 'Set_Database_Permissions_SQLAdmin_DB01' + { + ServerName = 'sql01.company.local' + InstanceName = 'DSC' + DatabaseName = 'DB01' + Name = 'CONTOSO\SQLAdmin' + Credential = $SqlAdministratorCredential + PermissionToExclude = @( + DatabasePermission + { + State = 'Grant' + Permission = @('Delete') + } + ) + } + } +} diff --git a/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToInclude.ps1 b/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToInclude.ps1 new file mode 100644 index 000000000..9a013d1cc --- /dev/null +++ b/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToInclude.ps1 @@ -0,0 +1,37 @@ +<# + .DESCRIPTION + This example shows how to ensure that the user account CONTOSO\SQLAdmin + is granted "Connect" and "Update" permissions for the databases DB01. + Any existing permissions in the states Grant, Deny, and GrantWithGrant will + not be changed (unless the contradict with the desired state). +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost + { + SqlDatabasePermission 'Set_Database_Permissions_SQLAdmin_DB01' + { + ServerName = 'sql01.company.local' + InstanceName = 'DSC' + DatabaseName = 'DB01' + Name = 'CONTOSO\SQLAdmin' + Credential = $SqlAdministratorCredential + PermissionToInclude = @( + DatabasePermission + { + State = 'Grant' + Permission = @('Connect', 'Update') + } + ) + } + } +} From 07446cfa730254ed9794bcbab3c77b3257a37026 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 24 Jul 2022 12:07:05 +0200 Subject: [PATCH 68/98] Fix integration test --- .../DSC_SqlDatabasePermission.Integration.Tests.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 165788c76..720c97212 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -897,7 +897,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' - $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}], but was [{"State":"Grant","Permission":["connect","update","alter"]}},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}], but was [{"State":"Grant","Permission":["alter", "connect","update"]}},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' # TODO: Remove this Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) @@ -1074,7 +1074,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' - $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]}},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}]' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}]' # TODO: Remove this Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) @@ -1245,7 +1245,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' - $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]}},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}]' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}]' # TODO: Remove this Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) From 2549513c4e302561dfbdb69c08b8c624adc10deb Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 24 Jul 2022 13:06:20 +0200 Subject: [PATCH 69/98] Fix integration test --- .../Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 720c97212..1a2319596 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -897,7 +897,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' - $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}], but was [{"State":"Grant","Permission":["alter", "connect","update"]}},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}], but was [{"State":"Grant","Permission":["alter","connect","update"]}},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' # TODO: Remove this Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) From 18857230142e6c5a04e2adabbac45f70f08986f4 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 24 Jul 2022 14:25:31 +0200 Subject: [PATCH 70/98] Fix integration tests --- source/Classes/020.SqlDatabasePermission.ps1 | 9 +++++++++ .../DSC_SqlDatabasePermission.Integration.Tests.ps1 | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 1f07920f8..0381bf416 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -327,6 +327,15 @@ class SqlDatabasePermission : ResourceBase [DatabasePermission[]] $currentState.Permission += $databasePermission } + + # TODO: This need to be done for other permission properties as well. + <# + Sort the permissions so they are in the order Grant, GrantWithGrant, + and Deny. It is because tests that evaluates property $Reasons + can know the expected order. If there is a better way of handling + tests for Reasons this can be removed. + #> + $currentState.Permission = $currentState.Permission | Sort-Object } # Always return all State; 'Grant', 'GrantWithGrant', and 'Deny'. diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 1a2319596..ce739e721 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -897,7 +897,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' - $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}], but was [{"State":"Grant","Permission":["alter","connect","update"]}},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}], but was [{"State":"Grant","Permission":["alter","connect","update"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' # TODO: Remove this Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) From 1d7200b22d8ae6cdd2b4a08252f295af6b3ef8f3 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 24 Jul 2022 15:56:42 +0200 Subject: [PATCH 71/98] Fix integration test --- source/Classes/020.SqlDatabasePermission.ps1 | 16 ++++++++-------- ...C_SqlDatabasePermission.Integration.Tests.ps1 | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 0381bf416..844c9beec 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -328,14 +328,14 @@ class SqlDatabasePermission : ResourceBase [DatabasePermission[]] $currentState.Permission += $databasePermission } - # TODO: This need to be done for other permission properties as well. - <# - Sort the permissions so they are in the order Grant, GrantWithGrant, - and Deny. It is because tests that evaluates property $Reasons - can know the expected order. If there is a better way of handling - tests for Reasons this can be removed. - #> - $currentState.Permission = $currentState.Permission | Sort-Object + # # TODO: This need to be done for other permission properties as well. + # <# + # Sort the permissions so they are in the order Grant, GrantWithGrant, + # and Deny. It is because tests that evaluates property $Reasons + # can know the expected order. If there is a better way of handling + # tests for Reasons this can be removed. + # #> + # $currentState.Permission = $currentState.Permission | Sort-Object } # Always return all State; 'Grant', 'GrantWithGrant', and 'Deny'. diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index ce739e721..79fd243f2 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -1074,7 +1074,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' - $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}]' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"Deny","Permission":[]},{"State":"GrantWithGrant","Permission":["select"]}], but was [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}]' # TODO: Remove this Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) From f6153e04fdd33f85c12f47a201d118e0adf9b35d Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 24 Jul 2022 16:34:57 +0200 Subject: [PATCH 72/98] Fix integration tests --- ...qlDatabasePermission.Integration.Tests.ps1 | 377 +++++++++++++++++- 1 file changed, 372 insertions(+), 5 deletions(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 79fd243f2..66a20f932 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -647,11 +647,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', Test 4: Assigning permission Connect for the state Grant. Testing: From previous test the permission Select will be revoked. - - TODO: MOVE THIS - Test 5: PermissionToInclude - - Test 6: PermissionToExclude #> Context 'When assigning parameter Permission' { <# @@ -1354,5 +1349,377 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } } } + + <# + The tests for PermissionToExclude after this Context-block is dependent + on that this test leaves the expected permission. If these tests for + PermissionToInclude is changed, make sure the tests for PermissionToExclude + is updated as necessary as well. + + Test 1: Assigning the permission Update to state Grant, and the + permission Select to the state GrantWithGrant, and the + permission Delete to the state Deny. + Testing: Adding permission to all states, and testing to handle + the permission Connect that already exist from previous tests. + #> + Context 'When assigning parameter PermissionToInclude' { + # Test 1 + Context 'When only specifying permissions for state Grant' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.PermissionToInclude = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'update' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = @( + 'select' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = @( + 'delete' + ) + }) + ) + } + + Context 'When the system is not in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:PermissionToInclude' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property PermissionToInclude should be [{"State":"Grant","Permission":["update"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":["delete"]}], but was [{"State":"Grant","Permission":[]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + + # TODO: Remove this + Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) + } + + It 'Should run method Test() and return the state as $false' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeFalse + } + + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + + Context 'When the system is in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 2 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -HaveCount 1 + $grantWithGrantState.Permission | Should -Contain 'Select' + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 1 + $denyState.Permission | Should -Contain 'Delete' + + $resourceCurrentState.Reasons | Should -BeNullOrEmpty + } + + It 'Should run method Test() and return the state as $true' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeTrue + } + + <# + This test is meant to validate that method Set() also evaluates + the current state against the desired state, and if they match + the Set() method returns without calling Set-SqlDscDatabasePermission + to change permissions. + + It is not possible to validate that Set-SqlDscDatabasePermission + is not call since it is not possible to mock the command in + the session when LCM runs (which Invoke-DscResource invokes). + There are no other indications that can be caught to validate + this, unless looking for the verbose output that says that + all properties are in desired state. + #> + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + } + } + + <# + The tests will leave Connect as granted permission for the principal. + + Test 1: Revoking the permission Update for state Grant, and the + permission Select for the state GrantWithGrant, and the + permission Delete for the state Deny. + Testing: Revoking permission to all states, and testing that + the permission Connect is left in state Grant. + #> + Context 'When assigning parameter PermissionToExclude' { + # Test 1 + Context 'When only specifying permissions for state Grant' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.PermissionToExclude = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'update' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = @( + 'select' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = @( + 'delete' + ) + }) + ) + } + + Context 'When the system is not in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 2 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -HaveCount 1 + $grantWithGrantState.Permission | Should -Contain 'Select' + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 1 + $denyState.Permission | Should -Contain 'Delete' + + + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:PermissionToExclude' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property PermissionToExclude should be [{"State":"Grant","Permission":["update"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":["delete"]}], but was [{"State":"Grant","Permission":[]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + + # TODO: Remove this + Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) + } + + It 'Should run method Test() and return the state as $false' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeFalse + } + + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + + Context 'When the system is in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + $resourceCurrentState.Reasons | Should -BeNullOrEmpty + } + + It 'Should run method Test() and return the state as $true' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeTrue + } + + <# + This test is meant to validate that method Set() also evaluates + the current state against the desired state, and if they match + the Set() method returns without calling Set-SqlDscDatabasePermission + to change permissions. + + It is not possible to validate that Set-SqlDscDatabasePermission + is not call since it is not possible to mock the command in + the session when LCM runs (which Invoke-DscResource invokes). + There are no other indications that can be caught to validate + this, unless looking for the verbose output that says that + all properties are in desired state. + #> + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + } + } } } From a6077517ab6bc4bdae948f953048462f4b861b0c Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 24 Jul 2022 16:59:17 +0200 Subject: [PATCH 73/98] Fix integration tests --- .../Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 66a20f932..37c54c826 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -1069,7 +1069,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' - $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"Deny","Permission":[]},{"State":"GrantWithGrant","Permission":["select"]}], but was [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}]' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}]' # TODO: Remove this Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) From b21f174ce2840da80aaafa4dc1b5757ec0cb8ce9 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 24 Jul 2022 18:05:00 +0200 Subject: [PATCH 74/98] Fix integration tests --- ...qlDatabasePermission.Integration.Tests.ps1 | 72 +++++++++++++++++-- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 37c54c826..6237b2a3f 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -1069,7 +1069,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' - $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}]' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]},{"State":"Deny","Permission":["delete","update"]},{"State":"GrantWithGrant","Permission":[]}]' # TODO: Remove this Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) @@ -1406,9 +1406,9 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name $resourceCurrentState.Permission | Should -HaveCount 3 - $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + # Property Permission $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) $grantState.State | Should -Be 'Grant' $grantState.Permission | Should -HaveCount 1 @@ -1422,6 +1422,20 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $denyState.State | Should -Be 'Deny' $denyState.Permission | Should -BeNullOrEmpty + # Property PermissionToInclude + $grantState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -BeNullOrEmpty + + $grantWithGrantState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + # Property Reasons $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:PermissionToInclude' $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property PermissionToInclude should be [{"State":"Grant","Permission":["update"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":["delete"]}], but was [{"State":"Grant","Permission":[]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' @@ -1473,9 +1487,9 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name $resourceCurrentState.Permission | Should -HaveCount 3 - $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + # Property Permission $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) $grantState.State | Should -Be 'Grant' $grantState.Permission | Should -HaveCount 2 @@ -1492,6 +1506,24 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $denyState.Permission | Should -HaveCount 1 $denyState.Permission | Should -Contain 'Delete' + # Property PermissionToInclude + $grantState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -HaveCount 1 + $grantWithGrantState.Permission | Should -Contain 'Select' + + $denyState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'GrDenyant' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 1 + $denyState.Permission | Should -Contain 'Delete' + $denyState.Permission | Should -BeNullOrEmpty + + # Property Reasons $resourceCurrentState.Reasons | Should -BeNullOrEmpty } @@ -1591,8 +1623,8 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name $resourceCurrentState.Permission | Should -HaveCount 3 $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty - $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + # Property Permission $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) $grantState.State | Should -Be 'Grant' $grantState.Permission | Should -HaveCount 2 @@ -1609,7 +1641,20 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $denyState.Permission | Should -HaveCount 1 $denyState.Permission | Should -Contain 'Delete' + # Property PermissionToExclude + $grantState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -BeNullOrEmpty + + $grantWithGrantState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + $denyState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + # Property Reasons $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:PermissionToExclude' $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property PermissionToExclude should be [{"State":"Grant","Permission":["update"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":["delete"]}], but was [{"State":"Grant","Permission":[]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' @@ -1662,8 +1707,8 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name $resourceCurrentState.Permission | Should -HaveCount 3 $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty - $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + # Property Permission $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) $grantState.State | Should -Be 'Grant' $grantState.Permission | Should -HaveCount 1 @@ -1677,6 +1722,23 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $denyState.State | Should -Be 'Deny' $denyState.Permission | Should -BeNullOrEmpty + # Property PermissionToExclude + $grantState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -HaveCount 1 + $grantWithGrantState.Permission | Should -Contain 'Select' + + $denyState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'GrDenyant' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 1 + $denyState.Permission | Should -Contain 'Delete' + + # Property Reasons $resourceCurrentState.Reasons | Should -BeNullOrEmpty } From 7ca699e1dbd7d716ae234fc2f9477eb3951cfb98 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 24 Jul 2022 19:08:57 +0200 Subject: [PATCH 75/98] Fix integration tests --- .../DSC_SqlDatabasePermission.Integration.Tests.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 6237b2a3f..68cb74d55 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -1517,11 +1517,10 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $grantWithGrantState.Permission | Should -HaveCount 1 $grantWithGrantState.Permission | Should -Contain 'Select' - $denyState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'GrDenyant' }) + $denyState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'Deny' }) $denyState.State | Should -Be 'Deny' $denyState.Permission | Should -HaveCount 1 $denyState.Permission | Should -Contain 'Delete' - $denyState.Permission | Should -BeNullOrEmpty # Property Reasons $resourceCurrentState.Reasons | Should -BeNullOrEmpty @@ -1733,7 +1732,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $grantWithGrantState.Permission | Should -HaveCount 1 $grantWithGrantState.Permission | Should -Contain 'Select' - $denyState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'GrDenyant' }) + $denyState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'Deny' }) $denyState.State | Should -Be 'Deny' $denyState.Permission | Should -HaveCount 1 $denyState.Permission | Should -Contain 'Delete' From 5830acf6974c5b573f886ff7ec7070ad7be03b86 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 25 Jul 2022 08:45:54 +0200 Subject: [PATCH 76/98] Cleanup class DatabasePermission --- source/Classes/002.DatabasePermission.ps1 | 71 ++++++++++++++++++- source/Classes/020.SqlDatabasePermission.ps1 | 7 +- .../Classes/SqlDatabasePermission.Tests.ps1 | 4 +- tests/Unit/Stubs/SMO.cs | 2 +- 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/source/Classes/002.DatabasePermission.ps1 b/source/Classes/002.DatabasePermission.ps1 index cfc20236e..bd0b40529 100644 --- a/source/Classes/002.DatabasePermission.ps1 +++ b/source/Classes/002.DatabasePermission.ps1 @@ -39,9 +39,77 @@ class DatabasePermission : IComparable, System.IEquatable[Object] [System.String] $State - # TODO: Can we use a validate set for the permissions? [DscProperty(Mandatory)] [AllowEmptyCollection()] + [ValidateSet( + 'Alter', + 'AlterAnyAsymmetricKey', + 'AlterAnyApplicationRole', + 'AlterAnyAssembly', + 'AlterAnyCertificate', + 'AlterAnyDatabaseAudit', + 'AlterAnyDataspace', + 'AlterAnyDatabaseEventNotification', + 'AlterAnyExternalDataSource', + 'AlterAnyExternalFileFormat', + 'AlterAnyFulltextCatalog', + 'AlterAnyMask', + 'AlterAnyMessageType', + 'AlterAnyRole', + 'AlterAnyRoute', + 'AlterAnyRemoteServiceBinding', + 'AlterAnyContract', + 'AlterAnySymmetricKey', + 'AlterAnySchema', + 'AlterAnySecurityPolicy', + 'AlterAnyService', + 'AlterAnyDatabaseDdlTrigger', + 'AlterAnyUser', + 'Authenticate', + 'BackupDatabase', + 'BackupLog', + 'Control', + 'Connect', + 'ConnectReplication', + 'Checkpoint', + 'CreateAggregate', + 'CreateAsymmetricKey', + 'CreateAssembly', + 'CreateCertificate', + 'CreateDatabase', + 'CreateDefault', + 'CreateDatabaseDdlEventNotification', + 'CreateFunction', + 'CreateFulltextCatalog', + 'CreateMessageType', + 'CreateProcedure', + 'CreateQueue', + 'CreateRole', + 'CreateRoute', + 'CreateRule', + 'CreateRemoteServiceBinding', + 'CreateContract', + 'CreateSymmetricKey', + 'CreateSchema', + 'CreateSynonym', + 'CreateService', + 'CreateTable', + 'CreateType', + 'CreateView', + 'CreateXmlSchemaCollection', + 'Delete', + 'Execute', + 'Insert', + 'References', + 'Select', + 'Showplan', + 'SubscribeQueryNotifications', + 'TakeOwnership', + 'Unmask', + 'Update', + 'ViewDefinition', + 'ViewDatabaseState' + )] [System.String[]] $Permission @@ -124,7 +192,6 @@ class DatabasePermission : IComparable, System.IEquatable[Object] } else { - # TODO: This should be an terminating error as an ArgumentException. $errorMessage = $script:localizedData.InvalidTypeForCompare -f @( $this.GetType().FullName, $object.GetType().FullName diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 844c9beec..0d006360d 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -44,10 +44,11 @@ ### Invalid values during compilation The parameter Permission is of type `[DatabasePermission]`. If a property - in the type is set to an invalid value an error will occur, this is expected. + in the type is set to an invalid value an error will occur, correct the + values in the properties to valid values. This happens when the values are validated against the `[ValidateSet()]` - of the resource. In such case the following error will be thrown from - PowerShell DSC during the compilation of the configuration: + of the resource. When there is an invalid value the following error will + be thrown when the configuration is run (it will not show during compilation): ```plaintext Failed to create an object of PowerShell class SqlDatabasePermission. diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index 0f4992f9e..958febcb9 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -1267,7 +1267,7 @@ Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { } [DatabasePermission] @{ State = 'Deny' - Permission = @('Create') + Permission = @('CreateDatabase') } ) } @@ -1319,7 +1319,7 @@ Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { # Revoking Denies Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { - $State -eq 'Revoke' -and $Permission.Create -eq $true + $State -eq 'Revoke' -and $Permission.CreateDatabase -eq $true } -Exactly -Times 1 -Scope It # Adding new Grant diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index 45b1f6154..e68f16b50 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -224,7 +224,7 @@ public DatabasePermissionSet( bool connect, bool update, bool select, bool inser public bool Select = false; public bool Insert = false; public bool Alter = false; - public bool Create = false; + public bool CreateDatabase = false; public bool Delete = false; } From 4f58f5613b1167e7b70445d2f89c7ef5bf2e1b34 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 25 Jul 2022 08:51:10 +0200 Subject: [PATCH 77/98] Cleanup tests --- ...SqlDatabasePermission.Integration.Tests.ps1 | 18 ------------------ tests/QA/ScriptAnalyzer.Tests.ps1 | 2 +- .../Unit/Classes/DatabasePermission.Tests.ps1 | 16 ---------------- 3 files changed, 1 insertion(+), 35 deletions(-) diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 68cb74d55..66454bdef 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -714,9 +714,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect","update","alter"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["Connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' - - # TODO: Remove this - Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) } It 'Should run method Test() and return the state as $false' { @@ -893,9 +890,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}], but was [{"State":"Grant","Permission":["alter","connect","update"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' - - # TODO: Remove this - Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) } It 'Should run method Test() and return the state as $false' { @@ -1070,9 +1064,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]},{"State":"Deny","Permission":["delete","update"]},{"State":"GrantWithGrant","Permission":[]}]' - - # TODO: Remove this - Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) } It 'Should run method Test() and return the state as $false' { @@ -1241,9 +1232,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}]' - - # TODO: Remove this - Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) } It 'Should run method Test() and return the state as $false' { @@ -1439,9 +1427,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:PermissionToInclude' $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property PermissionToInclude should be [{"State":"Grant","Permission":["update"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":["delete"]}], but was [{"State":"Grant","Permission":[]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' - - # TODO: Remove this - Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) } It 'Should run method Test() and return the state as $false' { @@ -1657,9 +1642,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Reasons | Should -HaveCount 1 $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:PermissionToExclude' $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property PermissionToExclude should be [{"State":"Grant","Permission":["update"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":["delete"]}], but was [{"State":"Grant","Permission":[]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' - - # TODO: Remove this - Write-Verbose -Verbose -Message ($resourceCurrentState.Reasons[0].Phrase | Out-String) } It 'Should run method Test() and return the state as $false' { diff --git a/tests/QA/ScriptAnalyzer.Tests.ps1 b/tests/QA/ScriptAnalyzer.Tests.ps1 index cc5b43f4e..26c740a48 100644 --- a/tests/QA/ScriptAnalyzer.Tests.ps1 +++ b/tests/QA/ScriptAnalyzer.Tests.ps1 @@ -58,7 +58,7 @@ Describe 'Script Analyzer Rules' { until it is properly excluded for source files, and instead only ran for the built module script module file (SqlServerDsc.psm1). #> - $pssaError = $pssaError | Where-Object -FilterScript { $_.RuleName -ne 'TypeNotFound'} + $pssaError = $pssaError | Where-Object -FilterScript { $_.RuleName -ne 'TypeNotFound' } $report = $pssaError | Format-Table -AutoSize | Out-String -Width 200 $pssaError | Should -HaveCount 0 -Because "all script analyzer rules should pass.`r`n`r`n $report`r`n" diff --git a/tests/Unit/Classes/DatabasePermission.Tests.ps1 b/tests/Unit/Classes/DatabasePermission.Tests.ps1 index 0d488af33..b0cdbef28 100644 --- a/tests/Unit/Classes/DatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/DatabasePermission.Tests.ps1 @@ -75,22 +75,6 @@ Describe 'DatabasePermission' -Tag 'DatabasePermission' { } Context 'When comparing two objects using method Equals()' { - # TODO: See comment in code regarding the code this test was suppose to cover. - # Context 'When the object to compare against is the wrong type' { - # It 'Should throw an error on compare' { - # InModuleScope -ScriptBlock { - # $databasPermissionInstance = [DatabasePermission]::new() - - # $databasPermissionInstance.State = 'Grant' - # $databasPermissionInstance.Permission = 'select' - - # # Must escape the brackets with ` for expected message comparison to work. - # { $databasPermissionInstance -eq 'invalid type' } | - # Should -Throw -ExpectedMessage 'Invalid type in comparison. Expected type `[DatabasePermission`], but the type was `[System.String`].' - # } - # } - # } - Context 'When both objects are equal' { Context 'When property Permission has a single value' { It 'Should return $true' { From 3e64589221a9743fc3e7ef23ae728beac485288d Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 25 Jul 2022 09:01:48 +0200 Subject: [PATCH 78/98] Cleanup Test-ResourceHasProperty --- source/Private/Test-ResourceHasProperty.ps1 | 21 +------------ .../Test-ResourceHasProperty.Tests.ps1 | 30 +++++++++---------- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/source/Private/Test-ResourceHasProperty.ps1 b/source/Private/Test-ResourceHasProperty.ps1 index abfe86847..0c7ae5a74 100644 --- a/source/Private/Test-ResourceHasProperty.ps1 +++ b/source/Private/Test-ResourceHasProperty.ps1 @@ -40,30 +40,11 @@ function Test-ResourceHasProperty $hasProperty = $false - # TODO: This should call Get-DscProperty instead. - - # Check to see if the property exist and that is it a DSC property. - $isDscProperty = $InputObject | - Get-Member -MemberType 'Property' -Name $Name | - Where-Object -FilterScript { - $InputObject.GetType().GetMember($_.Name).CustomAttributes.Where( - { - $_.AttributeType.Name -eq 'DscPropertyAttribute' - } - ) - } + $isDscProperty = (Get-DscProperty @PSBoundParameters).ContainsKey($Name) if ($isDscProperty) { $hasProperty = $true - - if ($HasValue.IsPresent) - { - if ($null -eq $InputObject.$Name) - { - $hasProperty = $false - } - } } return $hasProperty diff --git a/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 b/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 index 3a4e95430..271a2e172 100644 --- a/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 +++ b/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 @@ -212,16 +212,15 @@ $script:mockResourceBaseInstance = [MyMockResource] @{ } } } - } - Context 'When DSC property has a non-null value' { - BeforeAll { - <# - Must use a here-string because we need to pass 'using' which must be - first in a scriptblock, but if it is outside the here-string then PowerShell - PowerShell will fail to parse the test script. - #> - $inModuleScopeScriptBlock = @' + Context 'When DSC property has a null value' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' class MyMockResource { [DscProperty(Key)] @@ -244,14 +243,15 @@ $MyResourceReadProperty $script:mockResourceBaseInstance = [MyMockResource] @{} '@ - InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) - } + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } - It 'Should return the correct value' { - InModuleScope -ScriptBlock { - $result = Test-ResourceHasProperty -Name 'MyProperty3' -HasValue -InputObject $script:mockResourceBaseInstance + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceHasProperty -Name 'MyProperty3' -HasValue -InputObject $script:mockResourceBaseInstance - $result | Should -BeFalse + $result | Should -BeFalse + } } } } From 8c6d5018852d03756ecf6a409570824b0768a67e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 25 Jul 2022 09:20:57 +0200 Subject: [PATCH 79/98] Fix CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8221cb25e..8d5de78fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -217,7 +217,7 @@ class DSC_SqlConfiguration : OMI_BaseResource ### Composite or class-based resource Any composite (with a Configuration) or class-based resources should be prefixed -with just 'Sql' +with 'Sql' ### Localization From b0521649cb9a6b4c23f04ea3d854e3e750cfdd2d Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 25 Jul 2022 09:21:16 +0200 Subject: [PATCH 80/98] Update comment --- source/Public/Get-SqlDscDatabasePermission.ps1 | 2 ++ source/Public/Test-SqlDscIsDatabasePrincipal.ps1 | 2 ++ 2 files changed, 4 insertions(+) diff --git a/source/Public/Get-SqlDscDatabasePermission.ps1 b/source/Public/Get-SqlDscDatabasePermission.ps1 index 78dff6544..84bf25fb7 100644 --- a/source/Public/Get-SqlDscDatabasePermission.ps1 +++ b/source/Public/Get-SqlDscDatabasePermission.ps1 @@ -35,6 +35,8 @@ function Get-SqlDscDatabasePermission The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 When QA test run it loads the stub SMO classes so that the rule passes. + To get the rule to pass in the editor, in the Integrated Console run: + Add-Type -Path 'Tests/Unit/Stubs/SMO.cs' #> [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] [CmdletBinding()] diff --git a/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 b/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 index 8fb9ea110..99a4ff608 100644 --- a/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 +++ b/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 @@ -39,6 +39,8 @@ function Test-SqlDscIsDatabasePrincipal The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 When QA test run it loads the stub SMO classes so that the rule passes. + To get the rule to pass in the editor, in the Integrated Console run: + Add-Type -Path 'Tests/Unit/Stubs/SMO.cs' #> [CmdletBinding()] [OutputType([System.Boolean])] From ce7246262be6e42d490eb388444640debbb42d67 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 25 Jul 2022 12:32:10 +0200 Subject: [PATCH 81/98] Cleanup Set-SqlDscDatabasePermission --- .vscode/analyzersettings.psd1 | 81 +++--- source/Classes/020.SqlDatabasePermission.ps1 | 2 + .../Public/Set-SqlDscDatabasePermission.ps1 | 62 +++-- source/en-US/SqlServerDsc.strings.psd1 | 6 +- .../Set-SqlDscDatabasePermission.Tests.ps1 | 240 +++++++++++++----- 5 files changed, 272 insertions(+), 119 deletions(-) diff --git a/.vscode/analyzersettings.psd1 b/.vscode/analyzersettings.psd1 index b73f0d1a7..7ac1eb88e 100644 --- a/.vscode/analyzersettings.psd1 +++ b/.vscode/analyzersettings.psd1 @@ -7,48 +7,49 @@ IncludeDefaultRules = $true IncludeRules = @( # DSC Community style guideline rules from the module ScriptAnalyzer. - 'PSAvoidDefaultValueForMandatoryParameter', - 'PSAvoidDefaultValueSwitchParameter', - 'PSAvoidInvokingEmptyMembers', - 'PSAvoidNullOrEmptyHelpMessageAttribute', - 'PSAvoidUsingCmdletAliases', - 'PSAvoidUsingComputerNameHardcoded', - 'PSAvoidUsingDeprecatedManifestFields', - 'PSAvoidUsingEmptyCatchBlock', - 'PSAvoidUsingInvokeExpression', - 'PSAvoidUsingPositionalParameters', - 'PSAvoidShouldContinueWithoutForce', - 'PSAvoidUsingWMICmdlet', - 'PSAvoidUsingWriteHost', - 'PSDSCReturnCorrectTypesForDSCFunctions', - 'PSDSCStandardDSCFunctionsInResource', - 'PSDSCUseIdenticalMandatoryParametersForDSC', - 'PSDSCUseIdenticalParametersForDSC', - 'PSMisleadingBacktick', - 'PSMissingModuleManifestField', - 'PSPossibleIncorrectComparisonWithNull', - 'PSProvideCommentHelp', - 'PSReservedCmdletChar', - 'PSReservedParams', - 'PSUseApprovedVerbs', - 'PSUseCmdletCorrectly', - 'PSUseOutputTypeCorrectly', - 'PSAvoidGlobalVars', - 'PSAvoidUsingConvertToSecureStringWithPlainText', - 'PSAvoidUsingPlainTextForPassword', - 'PSAvoidUsingUsernameAndPasswordParams', - 'PSDSCUseVerboseMessageInDSCResource', - 'PSShouldProcess', - 'PSUseDeclaredVarsMoreThanAssignments', - 'PSUsePSCredentialType', + 'PSAvoidDefaultValueForMandatoryParameter' + 'PSAvoidDefaultValueSwitchParameter' + 'PSAvoidInvokingEmptyMembers' + 'PSAvoidNullOrEmptyHelpMessageAttribute' + 'PSAvoidUsingCmdletAliases' + 'PSAvoidUsingComputerNameHardcoded' + 'PSAvoidUsingDeprecatedManifestFields' + 'PSAvoidUsingEmptyCatchBlock' + 'PSAvoidUsingInvokeExpression' + 'PSAvoidUsingPositionalParameters' + 'PSAvoidShouldContinueWithoutForce' + 'PSAvoidUsingWMICmdlet' + 'PSAvoidUsingWriteHost' + 'PSDSCReturnCorrectTypesForDSCFunctions' + 'PSDSCStandardDSCFunctionsInResource' + 'PSDSCUseIdenticalMandatoryParametersForDSC' + 'PSDSCUseIdenticalParametersForDSC' + 'PSMisleadingBacktick' + 'PSMissingModuleManifestField' + 'PSPossibleIncorrectComparisonWithNull' + 'PSProvideCommentHelp' + 'PSReservedCmdletChar' + 'PSReservedParams' + 'PSUseApprovedVerbs' + 'PSUseCmdletCorrectly' + 'PSUseOutputTypeCorrectly' + 'PSAvoidGlobalVars' + 'PSAvoidUsingConvertToSecureStringWithPlainText' + 'PSAvoidUsingPlainTextForPassword' + 'PSAvoidUsingUsernameAndPasswordParams' + 'PSDSCUseVerboseMessageInDSCResource' + 'PSShouldProcess' + 'PSUseDeclaredVarsMoreThanAssignments' + 'PSUsePSCredentialType' # Additional rules from the module ScriptAnalyzer - 'PSUseConsistentWhitespace', - 'UseCorrectCasing', - 'PSPlaceOpenBrace', - 'PSPlaceCloseBrace', - 'AlignAssignmentStatement', - 'AvoidUsingDoubleQuotesForConstantString', + 'PSUseConsistentWhitespace' + 'UseCorrectCasing' + 'PSPlaceOpenBrace' + 'PSPlaceCloseBrace' + 'AlignAssignmentStatement' + 'AvoidUsingDoubleQuotesForConstantString' + 'UseShouldProcessForStateChangingFunctions' # Rules from the modules DscResource.AnalyzerRules and SqlServerDsc.AnalyzerRules 'Measure-*' diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 0d006360d..9aa24e5ad 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -589,6 +589,7 @@ class SqlDatabasePermission : ResourceBase Name = $this.Name Permission = $revokePermissionSet State = 'Revoke' + Force = $true } if ($currentStateToRevoke.State -eq 'GrantWithGrant') @@ -638,6 +639,7 @@ class SqlDatabasePermission : ResourceBase DatabaseName = $this.DatabaseName Name = $this.Name Permission = $permissionSet + Force = $true } try diff --git a/source/Public/Set-SqlDscDatabasePermission.ps1 b/source/Public/Set-SqlDscDatabasePermission.ps1 index dd8132a8a..be67e95da 100644 --- a/source/Public/Set-SqlDscDatabasePermission.ps1 +++ b/source/Public/Set-SqlDscDatabasePermission.ps1 @@ -25,6 +25,9 @@ **State** is set to `Revoke` the right to grant will also be revoked, and the revocation will cascade. + .PARAMETER Permission + Specifies that the permissions will + .OUTPUTS None. @@ -46,11 +49,6 @@ ignore if the database (parameter **DatabaseName**) is not present or the database principal is not present. If specifying `-ErrorAction 'Stop'` the command will throw an error if the database or database principal is missing. - - # TODO: This command should support ShouldProcess and Force parameter. Also, - the ScriptAnalyzer rule should be run on Public functions. - - # TODO: Document the public commands using PlatyPS, might use Sampler/ActiveDirectoryDsc as an example? #> function Set-SqlDscDatabasePermission { @@ -58,9 +56,11 @@ function Set-SqlDscDatabasePermission The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 When QA test run it loads the stub SMO classes so that the rule passes. + To get the rule to pass in the editor, in the Integrated Console run: + Add-Type -Path 'Tests/Unit/Stubs/SMO.cs' #> [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType()] param ( @@ -87,10 +87,23 @@ function Set-SqlDscDatabasePermission [Parameter()] [System.Management.Automation.SwitchParameter] - $WithGrant + $WithGrant, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force ) - # TODO: Assert properties to WithGrant it only possible for Grant and Revoke + if ($State -eq 'Deny' -and $WithGrant.IsPresent) + { + Write-Warning -Message $script:localizedData.DatabasePermission_IgnoreWithGrantForStateDeny + } + + if ($Force.IsPresent) + { + $ConfirmPreference = 'None' + } + $sqlDatabaseObject = $null if ($ServerObject.Databases) @@ -111,11 +124,6 @@ function Set-SqlDscDatabasePermission if ($isDatabasePrincipal) { - # TODO: Set permissions. - Write-Verbose -Message ( - $script:localizedData.DatabasePermission_ChangePermissionForUser -f $Name, $DatabaseName, $ServerObject.InstanceName - ) - # Get the permissions names that are set to $true in the DatabasePermissionSet. $permissionName = $Permission | Get-Member -MemberType 'Property' | @@ -124,6 +132,16 @@ function Set-SqlDscDatabasePermission $Permission.$_ } + $changePermissionShouldProcessVerboseDescriptionMessage = $script:localizedData.DatabasePermission_ChangePermissionShouldProcessVerboseDescription -f $Name, $DatabaseName, $ServerObject.InstanceName + $changePermissionShouldProcessVerboseWarningMessage = $script:localizedData.DatabasePermission_ChangePermissionShouldProcessVerboseWarning -f $Name + $changePermissionShouldProcessCaptionMessage = $script:localizedData.DatabasePermission_ChangePermissionShouldProcessCaption + + if (-not $PSCmdlet.ShouldProcess($changePermissionShouldProcessVerboseDescriptionMessage, $changePermissionShouldProcessVerboseWarningMessage, $changePermissionShouldProcessCaptionMessage)) + { + # Return without doing anything if the user did not want to continue processing. + return + } + switch ($State) { 'Grant' @@ -172,13 +190,27 @@ function Set-SqlDscDatabasePermission { $missingPrincipalMessage = $script:localizedData.DatabasePermission_MissingPrincipal -f $Name, $DatabaseName - Write-Error -Message $missingPrincipalMessage -Category 'InvalidOperation' -ErrorId 'GSDDP0001' -TargetObject $Name + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $missingPrincipalMessage, + 'GSDDP0001', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $Name + ) + ) } } else { $missingDatabaseMessage = $script:localizedData.DatabasePermission_MissingDatabase -f $DatabaseName - Write-Error -Message $missingDatabaseMessage -Category 'InvalidOperation' -ErrorId 'GSDDP0002' -TargetObject $DatabaseName + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $missingDatabaseMessage, + 'GSDDP0002', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $DatabaseName + ) + ) } } diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 667601581..236d903e1 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -11,10 +11,14 @@ ConvertFrom-StringData @' DatabasePermission_MissingDatabase = The database '{0}' cannot be found. # Set-SqlDscDatabasePermission - DatabasePermission_ChangePermissionForUser = Changing the permission for the principal '{0}' in the database '{1}' on the instance '{2}'. DatabasePermission_GrantPermission = Grant the permissions '{0}' for the principal '{1}'. DatabasePermission_DenyPermission = Deny the permissions '{0}' for the principal '{1}'. DatabasePermission_RevokePermission = Revoke the permissions '{0}' for the principal '{1}'. + DatabasePermission_IgnoreWithGrantForStateDeny = The parameter WithGrant cannot be used together with the state Deny, the parameter WithGrant is ignored. + DatabasePermission_ChangePermissionShouldProcessVerboseDescription = Changing the permission for the principal '{0}' in the database '{1}' on the instance '{2}'. + DatabasePermission_ChangePermissionShouldProcessVerboseWarning = Are you sure you want you change the permission for the principal '{0}'? + ## This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + DatabasePermission_ChangePermissionShouldProcessCaption = Change permission on principal # Test-SqlDscIsDatabasePrincipal IsDatabasePrincipal_DatabaseMissing = The database '{0}' cannot be found. diff --git a/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 b/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 index dce9f4746..1cb04e2d9 100644 --- a/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 +++ b/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 @@ -61,24 +61,13 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } } - Context 'When specifying to throw on error' { - BeforeAll { - $mockErrorMessage = InModuleScope -ScriptBlock { - $script:localizedData.DatabasePermission_MissingDatabase - } - } - - It 'Should throw the correct error' { - { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | - Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingDatabase } - } - Context 'When ignoring the error' { - It 'Should not throw an exception and return $null' { - Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'SilentlyContinue' @mockDefaultParameters | - Should -BeNullOrEmpty - } + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') } } @@ -100,24 +89,14 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } } - Context 'When specifying to throw on error' { - BeforeAll { - $mockErrorMessage = InModuleScope -ScriptBlock { - $script:localizedData.DatabasePermission_MissingDatabase - } - } - It 'Should throw the correct error' { - { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | - Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingDatabase } - } - Context 'When ignoring the error' { - It 'Should not throw an exception and return $null' { - Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'SilentlyContinue' @mockDefaultParameters | - Should -BeNullOrEmpty - } + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') } } @@ -143,28 +122,137 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } } - Context 'When specifying to throw on error' { + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingPrincipal + } + + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'UnknownUser', 'AdventureWorks') + } + } + + Context 'When the database principal exist' { + Context 'When using parameter Confirm with value $false' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Deny' -Value { + $script:mockMethodDenyCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + Confirm = $false + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Deny' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodDenyCallCount = 0 + } + + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodDenyCallCount | Should -Be 1 + } + } + + Context 'When using parameter Force' { BeforeAll { - $mockErrorMessage = InModuleScope -ScriptBlock { - $script:localizedData.DatabasePermission_MissingPrincipal + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Deny' -Value { + $script:mockMethodDenyCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + Force = $true + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Deny' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } } } - It 'Should throw the correct error' { - { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | - Should -Throw -ExpectedMessage ($mockErrorMessage -f 'UnknownUser', 'AdventureWorks') + BeforeEach { + $script:mockMethodDenyCallCount = 0 + } + + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodDenyCallCount | Should -Be 1 } } - Context 'When ignoring the error' { - It 'Should not throw an exception and return $null' { - Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'SilentlyContinue' @mockDefaultParameters | - Should -BeNullOrEmpty + Context 'When using parameter WhatIf' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Deny' -Value { + $script:mockMethodDenyCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + WhatIf = $true + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Deny' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodDenyCallCount = 0 + } + + It 'Should not call the mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodDenyCallCount | Should -Be 0 } } - } - Context 'When the database principal exist' { Context 'When permission should be granted' { BeforeAll { $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | @@ -183,6 +271,7 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ + Confirm = $false DatabaseName = 'AdventureWorks' Name = 'Zebes\SamusAran' State = 'Grant' @@ -196,16 +285,16 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { $script:mockMethodGrantCallCount = 0 } - It 'Should return the correct values' { - { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | Should -Not -Throw $script:mockMethodGrantCallCount | Should -Be 1 } Context 'When passing ServerObject over the pipeline' { - It 'Should return the correct values' { - { $mockServerObject | Set-SqlDscDatabasePermission -ErrorAction 'Stop' @mockDefaultParameters } | + It 'Should call the correct mocked method' { + { $mockServerObject | Set-SqlDscDatabasePermission @mockDefaultParameters } | Should -Not -Throw $script:mockMethodGrantCallCount | Should -Be 1 @@ -246,6 +335,7 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ + Confirm = $false DatabaseName = 'AdventureWorks' Name = 'Zebes\SamusAran' State = 'Grant' @@ -260,16 +350,16 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { $script:mockMethodGrantUsingWithGrantCallCount = 0 } - It 'Should return the correct values' { - { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | Should -Not -Throw $script:mockMethodGrantUsingWithGrantCallCount | Should -Be 1 } Context 'When passing ServerObject over the pipeline' { - It 'Should return the correct values' { - { $mockServerObject | Set-SqlDscDatabasePermission -ErrorAction 'Stop' @mockDefaultParameters } | + It 'Should call the correct mocked method' { + { $mockServerObject | Set-SqlDscDatabasePermission @mockDefaultParameters } | Should -Not -Throw $script:mockMethodGrantUsingWithGrantCallCount | Should -Be 1 @@ -295,6 +385,7 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ + Confirm = $false DatabaseName = 'AdventureWorks' Name = 'Zebes\SamusAran' State = 'Revoke' @@ -308,16 +399,16 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { $script:mockMethodRevokeCallCount = 0 } - It 'Should return the correct values' { - { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | Should -Not -Throw $script:mockMethodRevokeCallCount | Should -Be 1 } Context 'When passing ServerObject over the pipeline' { - It 'Should return the correct values' { - { $mockServerObject | Set-SqlDscDatabasePermission -ErrorAction 'Stop' @mockDefaultParameters } | + It 'Should call the correct mocked method' { + { $mockServerObject | Set-SqlDscDatabasePermission @mockDefaultParameters } | Should -Not -Throw $script:mockMethodRevokeCallCount | Should -Be 1 @@ -361,6 +452,7 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ + Confirm = $false DatabaseName = 'AdventureWorks' Name = 'Zebes\SamusAran' State = 'Revoke' @@ -375,16 +467,16 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { $script:mockMethodRevokeUsingWithGrantCallCount = 0 } - It 'Should return the correct values' { - { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | Should -Not -Throw $script:mockMethodRevokeUsingWithGrantCallCount | Should -Be 1 } Context 'When passing ServerObject over the pipeline' { - It 'Should return the correct values' { - { $mockServerObject | Set-SqlDscDatabasePermission -ErrorAction 'Stop' @mockDefaultParameters } | + It 'Should call the correct mocked method' { + { $mockServerObject | Set-SqlDscDatabasePermission @mockDefaultParameters } | Should -Not -Throw $script:mockMethodGrantUsingWithGrantCallCount | Should -Be 1 @@ -410,6 +502,7 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ + Confirm = $false DatabaseName = 'AdventureWorks' Name = 'Zebes\SamusAran' State = 'Deny' @@ -423,19 +516,40 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { $script:mockMethodDenyCallCount = 0 } - It 'Should return the correct values' { - { Set-SqlDscDatabasePermission -ServerObject $mockServerObject -ErrorAction 'Stop' @mockDefaultParameters } | + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | Should -Not -Throw $script:mockMethodDenyCallCount | Should -Be 1 } Context 'When passing ServerObject over the pipeline' { - It 'Should return the correct values' { - { $mockServerObject | Set-SqlDscDatabasePermission -ErrorAction 'Stop' @mockDefaultParameters } | + It 'Should call the correct mocked method' { + { $mockServerObject | Set-SqlDscDatabasePermission @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodDenyCallCount | Should -Be 1 + } + } + + Context 'When passing WithGrant' { + BeforeAll { + Mock -CommandName Write-Warning + } + + It 'Should output the correct warning message and return the correct values' { + $mockWarningMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_IgnoreWithGrantForStateDeny + } + + { $mockServerObject | Set-SqlDscDatabasePermission -WithGrant @mockDefaultParameters } | Should -Not -Throw $script:mockMethodDenyCallCount | Should -Be 1 + + Should -Invoke -CommandName 'Write-Warning' -ParameterFilter { + $Message -eq $mockWarningMessage + } } } } From 86b9fb09cb4de2b0d8b491de6e29557bda84dc80 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 25 Jul 2022 14:28:52 +0200 Subject: [PATCH 82/98] Improved SqlDatabasePermission --- source/Classes/020.SqlDatabasePermission.ps1 | 81 +++++++++++--------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 9aa24e5ad..3e295de48 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -164,6 +164,14 @@ [DscResource(RunAsCredential = 'NotSupported')] class SqlDatabasePermission : ResourceBase { + <# + Property for holding the server connection object. + This should be an object of type [Microsoft.SqlServer.Management.Smo.Server] + but using that type fails the build process currently. + See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. + #> + hidden [System.Object] $sqlServerObject = $null + [DscProperty(Key)] [System.String] $InstanceName @@ -230,6 +238,34 @@ class SqlDatabasePermission : ResourceBase ([ResourceBase] $this).Set() } + <# + Returns and reuses the server connection object. If the server connection + object does not exist a connection to the SQL Server instance will occur. + + This should return an object of type [Microsoft.SqlServer.Management.Smo.Server] + but using that type fails the build process currently. + See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. + #> + hidden [System.Object] GetServerObject() + { + if (-not $this.sqlServerObject) + { + $connectSqlDscDatabaseEngineParameters = @{ + ServerName = $this.ServerName + InstanceName = $this.InstanceName + } + + if ($this.Credential) + { + $connectSqlDscDatabaseEngineParameters.Credential = $this.Credential + } + + $this.sqlServerObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters + } + + return $this.sqlServerObject + } + <# Base method Get() call this method to get the current state as a hashtable. The parameter properties will contain the key properties. @@ -241,9 +277,9 @@ class SqlDatabasePermission : ResourceBase if ($this.Credential) { <# - TODO: This does not work, Get() will return an empty PSCredential-object. - Using MOF-based resource variant does not work either as it throws - an error: https://github.com/dsccommunity/ActiveDirectoryDsc/blob/b2838d945204e1153cc3cbfca1a3d90671e0a61c/source/Modules/ActiveDirectoryDsc.Common/ActiveDirectoryDsc.Common.psm1#L1834-L1856 + This does not work, even if username is set, the method Get() will + return an empty PSCredential-object. Kept it here so it at least + return a Credential object. #> $currentStateCredential = [PSCredential]::new( $this.Credential.UserName, @@ -257,21 +293,7 @@ class SqlDatabasePermission : ResourceBase Permission = [DatabasePermission[]] @() } - $connectSqlDscDatabaseEngineParameters = @{ - ServerName = $this.ServerName - InstanceName = $properties.InstanceName - } - - if ($this.Credential) - { - $connectSqlDscDatabaseEngineParameters.Credential = $this.Credential - } - - # TODO: By adding a hidden property that holds the server object we only need to connect when that property is $null. - $serverObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters - - # TODO: TA BORT -VERBOSE! - Write-Verbose -Verbose -Message ( + Write-Verbose -Message ( $this.localizedData.EvaluateDatabasePermissionForPrincipal -f @( $properties.Name, $properties.DatabaseName, @@ -279,6 +301,8 @@ class SqlDatabasePermission : ResourceBase ) ) + $serverObject = $this.GetServerObject() + $databasePermissionInfo = $serverObject | Get-SqlDscDatabasePermission -DatabaseName $this.DatabaseName -Name $this.Name -ErrorAction 'SilentlyContinue' @@ -328,15 +352,6 @@ class SqlDatabasePermission : ResourceBase [DatabasePermission[]] $currentState.Permission += $databasePermission } - - # # TODO: This need to be done for other permission properties as well. - # <# - # Sort the permissions so they are in the order Grant, GrantWithGrant, - # and Deny. It is because tests that evaluates property $Reasons - # can know the expected order. If there is a better way of handling - # tests for Reasons this can be removed. - # #> - # $currentState.Permission = $currentState.Permission | Sort-Object } # Always return all State; 'Grant', 'GrantWithGrant', and 'Deny'. @@ -462,17 +477,7 @@ class SqlDatabasePermission : ResourceBase #> hidden [void] Modify([System.Collections.Hashtable] $properties) { - $connectSqlDscDatabaseEngineParameters = @{ - ServerName = $this.ServerName - InstanceName = $this.InstanceName - } - - if ($this.Credential) - { - $connectSqlDscDatabaseEngineParameters.Credential = $this.Credential - } - - $serverObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters + $serverObject = $this.GetServerObject() $testSqlDscIsDatabasePrincipalParameters = @{ ServerObject = $serverObject From 22c41d995884620490a5371a41c9f57ab8e82dbe Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 25 Jul 2022 14:29:22 +0200 Subject: [PATCH 83/98] Remove forced Verbose statements from ResourceBase --- source/Classes/010.ResourceBase.ps1 | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index cb758c022..af8616f2a 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -34,8 +34,7 @@ class ResourceBase # Get all key properties. $keyProperty = $this | Get-KeyProperty - # TODO: TA BORT -VERBOSE - Write-Verbose -Verbose -Message ($this.localizedData.GetCurrentState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + Write-Verbose -Message ($this.localizedData.GetCurrentState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) <# TODO: Should call back to the derived class for proper handling of adding @@ -173,8 +172,6 @@ class ResourceBase Code = '{0}:{0}:{1}' -f $this.GetType(), $property.Property Phrase = 'The property {0} should be {1}, but was {2}' -f $property.Property, ($propertyExpectedValue | ConvertTo-Json -Compress), ($propertyActualValue | ConvertTo-Json -Compress) } - - Write-Verbose -Verbose -Message ($dscResourceObject.Reasons | Out-String) } } } @@ -185,7 +182,7 @@ class ResourceBase [void] Set() { - Write-Verbose -Verbose -Message ($this.localizedData.SetDesiredState -f $this.GetType().Name, ($this | Get-KeyProperty | ConvertTo-Json -Compress)) + Write-Verbose -Message ($this.localizedData.SetDesiredState -f $this.GetType().Name, ($this | Get-KeyProperty | ConvertTo-Json -Compress)) $this.Assert() @@ -201,7 +198,7 @@ class ResourceBase $propertiesToModify.Keys | ForEach-Object -Process { - Write-Verbose -Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_) + Write-Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_) } <# @@ -212,13 +209,13 @@ class ResourceBase } else { - Write-Verbose -Verbose -Message $this.localizedData.NoPropertiesToSet + Write-Verbose -Message $this.localizedData.NoPropertiesToSet } } [System.Boolean] Test() { - Write-Verbose -Verbose -Message ($this.localizedData.TestDesiredState -f $this.GetType().Name, ($this | Get-KeyProperty | ConvertTo-Json -Compress)) + Write-Verbose -Message ($this.localizedData.TestDesiredState -f $this.GetType().Name, ($this | Get-KeyProperty | ConvertTo-Json -Compress)) $this.Assert() @@ -237,11 +234,11 @@ class ResourceBase if ($isInDesiredState) { - Write-Verbose -Verbose -Message $this.localizedData.InDesiredState + Write-Verbose $this.localizedData.InDesiredState } else { - Write-Verbose -Verbose -Message $this.localizedData.NotInDesiredState + Write-Verbose $this.localizedData.NotInDesiredState } return $isInDesiredState From 3910dbb9d0054d9eb35e32f4874073704fc94245 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 26 Jul 2022 13:03:50 +0200 Subject: [PATCH 84/98] Cleanup SMO stubs --- tests/Unit/Stubs/SMO.cs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index e68f16b50..5290b0a82 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -236,16 +236,16 @@ public class DatabasePermissionInfo { public DatabasePermissionInfo() { - Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet = { new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet() }; + Microsoft.SqlServer.Management.Smo.DatabasePermissionSet permissionSet = new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet(); this.PermissionType = permissionSet; } - public DatabasePermissionInfo( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet ) + public DatabasePermissionInfo( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet permissionSet ) { this.PermissionType = permissionSet; } - public Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] PermissionType; + public Microsoft.SqlServer.Management.Smo.DatabasePermissionSet PermissionType; public string PermissionState = "Grant"; } @@ -566,17 +566,6 @@ public Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[] EnumDatabaseP { List listOfDatabasePermissionInfo = new List(); - if( Globals.GenerateMockData ) { - Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet = { - new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet( true, false ), - new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet( false, true ) - }; - - listOfDatabasePermissionInfo.Add( new Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo( permissionSet ) ); - } else { - listOfDatabasePermissionInfo.Add( new Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo() ); - } - Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[] permissionInfo = listOfDatabasePermissionInfo.ToArray(); return permissionInfo; From 513b8437cd7a15591a3229e52b1e1b6f597b5424 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 26 Jul 2022 14:40:43 +0200 Subject: [PATCH 85/98] Move code to ConvertTo-DatabasePermission --- source/Classes/020.SqlDatabasePermission.ps1 | 45 +- .../Public/ConvertTo-DatabasePermission.ps1 | 106 ++++ .../Classes/SqlDatabasePermission.Tests.ps1 | 285 ++++----- .../ConvertTo-DatabasePermission.Tests.ps1 | 563 ++++++++++++++++++ 4 files changed, 803 insertions(+), 196 deletions(-) create mode 100644 source/Public/ConvertTo-DatabasePermission.ps1 create mode 100644 tests/Unit/Public/ConvertTo-DatabasePermission.Tests.ps1 diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 3e295de48..24189c447 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -310,48 +310,7 @@ class SqlDatabasePermission : ResourceBase if ($databasePermissionInfo) { # TODO: Below code could be a command ConvertTo-DatabasePermission that returns an array of [DatabasePermission] - <# - Single out all unique states since every single permission can be - one object in the $databasePermissionInfo - #> - $permissionState = @( - $databasePermissionInfo | ForEach-Object -Process { - # Convert from the type PermissionState to String. - [System.String] $_.PermissionState - } - ) | Select-Object -Unique - - foreach ($currentPermissionState in $permissionState) - { - $filteredDatabasePermission = $databasePermissionInfo | - Where-Object -FilterScript { - $_.PermissionState -eq $currentPermissionState - } - - $databasePermission = [DatabasePermission] @{ - State = $currentPermissionState - Permission = [System.String[]] @() - } - - foreach ($currentPermission in $filteredDatabasePermission) - { - # Get the permission names that is set to $true - $permissionProperty = $currentPermission.PermissionType | - Get-Member -MemberType 'Property' | - Select-Object -ExpandProperty 'Name' -Unique | - Where-Object -FilterScript { - $currentPermission.PermissionType.$_ - } - - - foreach ($currentPermissionProperty in $permissionProperty) - { - $databasePermission.Permission += $currentPermissionProperty - } - } - - [DatabasePermission[]] $currentState.Permission += $databasePermission - } + [DatabasePermission[]] $currentState.Permission = $databasePermissionInfo | ConvertTo-DatabasePermission } # Always return all State; 'Grant', 'GrantWithGrant', and 'Deny'. @@ -581,6 +540,7 @@ class SqlDatabasePermission : ResourceBase { foreach ($currentStateToRevoke in $permissionsToRevoke) { + # TODO: Make this a ConvertFrom-DatabasePermission $revokePermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' foreach ($revokePermissionName in $currentStateToRevoke.Permission) @@ -632,6 +592,7 @@ class SqlDatabasePermission : ResourceBase # If there is not an empty array, change permissions. if (-not [System.String]::IsNullOrEmpty($currentDesiredPermissionState.Permission)) { + # TODO: Make this a ConvertFrom-DatabasePermission $permissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' foreach ($permissionName in $currentDesiredPermissionState.Permission) diff --git a/source/Public/ConvertTo-DatabasePermission.ps1 b/source/Public/ConvertTo-DatabasePermission.ps1 new file mode 100644 index 000000000..e0476ffd0 --- /dev/null +++ b/source/Public/ConvertTo-DatabasePermission.ps1 @@ -0,0 +1,106 @@ +<# + .SYNOPSIS + Returns the current permissions for the database principal. + + .PARAMETER DatabasePermissionInfo + Specifies current server connection object. + + .PARAMETER DatabaseName + Specifies the database name. + + .PARAMETER Name + Specifies the name of the database principal for which the permissions are + returned. + + .OUTPUTS + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + $databasePermissionInfo = Get-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' + ConvertTo-DatabasePermission -DatabasePermissionInfo $databasePermissionInfo + + .NOTES +#> +function ConvertTo-DatabasePermission +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when the output type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [CmdletBinding()] + [OutputType([DatabasePermission[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [AllowEmptyCollection()] + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] + $DatabasePermissionInfo + ) + + begin + { + [DatabasePermission[]] $permissions = @() + } + + process + { + $permissionState = foreach ($currentDatabasePermissionInfo in $DatabasePermissionInfo) + { + # Convert from the type PermissionState to String. + [System.String] $currentDatabasePermissionInfo.PermissionState + } + + $permissionState = $permissionState | Select-Object -Unique + + foreach ($currentPermissionState in $permissionState) + { + $filteredDatabasePermission = $DatabasePermissionInfo | + Where-Object -FilterScript { + $_.PermissionState -eq $currentPermissionState + } + + $databasePermissionStateExist = $permissions.Where({ + $_.State -contains $currentPermissionState + }) | + Select-Object -First 1 + + if ($databasePermissionStateExist) + { + $databasePermission = $databasePermissionStateExist + } + else + { + $databasePermission = [DatabasePermission] @{ + State = $currentPermissionState + Permission = [System.String[]] @() + } + } + + foreach ($currentPermission in $filteredDatabasePermission) + { + # Get the permission names that is set to $true + $permissionProperty = $currentPermission.PermissionType | + Get-Member -MemberType 'Property' | + Select-Object -ExpandProperty 'Name' -Unique | + Where-Object -FilterScript { + $currentPermission.PermissionType.$_ + } + + + foreach ($currentPermissionProperty in $permissionProperty) + { + $databasePermission.Permission += $currentPermissionProperty + } + } + + # Only add the object if it was created. + if (-not $databasePermissionStateExist) + { + $permissions += $databasePermission + } + } + } + + end + { + return $permissions + } +} diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index 958febcb9..5260d3045 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -427,25 +427,27 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { } Mock -CommandName Get-SqlDscDatabasePermission -MockWith { - $mockDatabasePermissionInfo = @() - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - return $mockDatabasePermissionInfo + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Update = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Grant' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + return $mockDatabasePermissionInfoCollection } } @@ -500,33 +502,28 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { } Mock -CommandName Get-SqlDscDatabasePermission -MockWith { - $mockDatabasePermissionInfo = @() - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $false, $true, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Deny' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - return $mockDatabasePermissionInfo + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Update = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Select = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + return $mockDatabasePermissionInfoCollection } } @@ -585,33 +582,28 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { } Mock -CommandName Get-SqlDscDatabasePermission -MockWith { - $mockDatabasePermissionInfo = @() - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $false, $true, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Deny' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - return $mockDatabasePermissionInfo + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Update = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Select = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + return $mockDatabasePermissionInfoCollection } } @@ -673,33 +665,28 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { } Mock -CommandName Get-SqlDscDatabasePermission -MockWith { - $mockDatabasePermissionInfo = @() - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $false, $true, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Deny' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - return $mockDatabasePermissionInfo + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Update = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Select = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + return $mockDatabasePermissionInfoCollection } } @@ -763,33 +750,28 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { } Mock -CommandName Get-SqlDscDatabasePermission -MockWith { - $mockDatabasePermissionInfo = @() - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $false, $true, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Deny' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - return $mockDatabasePermissionInfo + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Update = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Select = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + return $mockDatabasePermissionInfoCollection } } @@ -851,33 +833,28 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { } Mock -CommandName Get-SqlDscDatabasePermission -MockWith { - $mockDatabasePermissionInfo = @() - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true, $false, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockDatabasePermissionInfo += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $false, $true, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Deny' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'MockUserName' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - return $mockDatabasePermissionInfo + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Update = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Select = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + return $mockDatabasePermissionInfoCollection } } diff --git a/tests/Unit/Public/ConvertTo-DatabasePermission.Tests.ps1 b/tests/Unit/Public/ConvertTo-DatabasePermission.Tests.ps1 new file mode 100644 index 000000000..a82ad4890 --- /dev/null +++ b/tests/Unit/Public/ConvertTo-DatabasePermission.Tests.ps1 @@ -0,0 +1,563 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { + Context 'When passing empty collection as PermissionInfo' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 0 + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + + $mockResult | Should -HaveCount 0 + } + } + } + + Context 'When permission state is only Grant' { + Context 'When the array contain a single PermissionInfo with a single permission' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet.Connect = $true + + $mockDatabasePermissionInfo = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo.PermissionState = 'Grant' + $mockDatabasePermissionInfo.PermissionType = $mockDatabasePermissionSet + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Grant' + $mockResult[0].Permission | Should -Contain 'Connect' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Grant' + $mockResult[0].Permission | Should -Contain 'Connect' + } + } + } + + Context 'When the array contain multiple PermissionInfo with a single permission' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Grant' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Grant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Grant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + } + } + } + + Context 'When the array contain multiple PermissionInfo with a multiple permissions' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Select = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + $mockDatabasePermissionSet2.Delete = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Grant' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Grant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + $mockResult[0].Permission | Should -Contain 'Select' + $mockResult[0].Permission | Should -Contain 'Delete' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Grant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + $mockResult[0].Permission | Should -Contain 'Select' + $mockResult[0].Permission | Should -Contain 'Delete' + } + } + } + } + + Context 'When permission state is only Deny' { + Context 'When the array contain a single PermissionInfo with a single permission' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet.Connect = $true + + $mockDatabasePermissionInfo = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo.PermissionState = 'Deny' + $mockDatabasePermissionInfo.PermissionType = $mockDatabasePermissionSet + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Deny' + $mockResult[0].Permission | Should -Contain 'Connect' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Deny' + $mockResult[0].Permission | Should -Contain 'Connect' + } + } + } + + Context 'When the array contain multiple PermissionInfo with a single permission' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Deny' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Deny' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Deny' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + } + } + } + + Context 'When the array contain multiple PermissionInfo with a multiple permissions' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Select = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Deny' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + $mockDatabasePermissionSet2.Delete = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Deny' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + $mockResult[0].Permission | Should -Contain 'Select' + $mockResult[0].Permission | Should -Contain 'Delete' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Deny' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + $mockResult[0].Permission | Should -Contain 'Select' + $mockResult[0].Permission | Should -Contain 'Delete' + } + } + } + } + + Context 'When permission state is only GrantWithGrant' { + Context 'When the array contain a single PermissionInfo with a single permission' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet.Connect = $true + + $mockDatabasePermissionInfo = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo.PermissionState = 'GrantWithGrant' + $mockDatabasePermissionInfo.PermissionType = $mockDatabasePermissionSet + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'GrantWithGrant' + $mockResult[0].Permission | Should -Contain 'Connect' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'GrantWithGrant' + $mockResult[0].Permission | Should -Contain 'Connect' + } + } + } + + Context 'When the array contain multiple PermissionInfo with a single permission' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'GrantWithGrant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'GrantWithGrant' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'GrantWithGrant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'GrantWithGrant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + } + } + } + + Context 'When the array contain multiple PermissionInfo with a multiple permissions' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Select = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'GrantWithGrant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + $mockDatabasePermissionSet2.Delete = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'GrantWithGrant' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'GrantWithGrant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + $mockResult[0].Permission | Should -Contain 'Select' + $mockResult[0].Permission | Should -Contain 'Delete' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'GrantWithGrant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + $mockResult[0].Permission | Should -Contain 'Select' + $mockResult[0].Permission | Should -Contain 'Delete' + } + } + } + } + + Context 'When permission state have all states Grant, GrantWithGrant, and Deny' { + Context 'When the array contain multiple PermissionInfo with a multiple permissions' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Select = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'GrantWithGrant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + $mockDatabasePermissionSet2.Delete = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Grant' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + $mockDatabasePermissionSet3 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet3.Update = $true + $mockDatabasePermissionSet3.Insert = $true + + $mockDatabasePermissionInfo3 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo3.PermissionState = 'Deny' + $mockDatabasePermissionInfo3.PermissionType = $mockDatabasePermissionSet3 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo3 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 3 + + $grantPermission = $mockResult.Where({ $_.State -eq 'Grant' }) + + $grantPermission.State | Should -Be 'Grant' + $grantPermission.Permission | Should -Contain 'Alter' + $grantPermission.Permission | Should -Contain 'Delete' + + $grantWithGrantPermission = $mockResult.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantPermission.State | Should -Be 'GrantWithGrant' + $grantWithGrantPermission.Permission | Should -Contain 'Connect' + $grantWithGrantPermission.Permission | Should -Contain 'Select' + + + $denyPermission = $mockResult.Where({ $_.State -eq 'Deny' }) + + $denyPermission.State | Should -Be 'Deny' + $denyPermission.Permission | Should -Contain 'Update' + $denyPermission.Permission | Should -Contain 'Insert' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + + $mockResult | Should -HaveCount 3 + + $grantPermission = $mockResult.Where({ $_.State -eq 'Grant' }) + + $grantPermission.State | Should -Be 'Grant' + $grantPermission.Permission | Should -Contain 'Alter' + $grantPermission.Permission | Should -Contain 'Delete' + + $grantWithGrantPermission = $mockResult.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantPermission.State | Should -Be 'GrantWithGrant' + $grantWithGrantPermission.Permission | Should -Contain 'Connect' + $grantWithGrantPermission.Permission | Should -Contain 'Select' + + + $denyPermission = $mockResult.Where({ $_.State -eq 'Deny' }) + + $denyPermission.State | Should -Be 'Deny' + $denyPermission.Permission | Should -Contain 'Update' + $denyPermission.Permission | Should -Contain 'Insert' + } + } + } + } +} From ccd59c8240aba9fe71310b0f5571fc3612efba1a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 26 Jul 2022 15:59:39 +0200 Subject: [PATCH 86/98] Add function ConvertFrom-DatabasePermission --- source/Classes/020.SqlDatabasePermission.ps1 | 16 +--- .../Public/ConvertFrom-DatabasePermission.ps1 | 47 +++++++++++ .../Public/ConvertTo-DatabasePermission.ps1 | 17 ++-- .../ConvertFrom-DatabasePermission.Tests.ps1 | 78 +++++++++++++++++++ 4 files changed, 132 insertions(+), 26 deletions(-) create mode 100644 source/Public/ConvertFrom-DatabasePermission.ps1 create mode 100644 tests/Unit/Public/ConvertFrom-DatabasePermission.Tests.ps1 diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 24189c447..1b430dbe7 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -540,13 +540,7 @@ class SqlDatabasePermission : ResourceBase { foreach ($currentStateToRevoke in $permissionsToRevoke) { - # TODO: Make this a ConvertFrom-DatabasePermission - $revokePermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' - - foreach ($revokePermissionName in $currentStateToRevoke.Permission) - { - $revokePermissionSet.$revokePermissionName = $true - } + $revokePermissionSet = $currentStateToRevoke | ConvertFrom-DatabasePermission $setSqlDscDatabasePermissionParameters = @{ ServerObject = $serverObject @@ -592,13 +586,7 @@ class SqlDatabasePermission : ResourceBase # If there is not an empty array, change permissions. if (-not [System.String]::IsNullOrEmpty($currentDesiredPermissionState.Permission)) { - # TODO: Make this a ConvertFrom-DatabasePermission - $permissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' - - foreach ($permissionName in $currentDesiredPermissionState.Permission) - { - $permissionSet.$permissionName = $true - } + $permissionSet = $currentDesiredPermissionState | ConvertFrom-DatabasePermission $setSqlDscDatabasePermissionParameters = @{ ServerObject = $serverObject diff --git a/source/Public/ConvertFrom-DatabasePermission.ps1 b/source/Public/ConvertFrom-DatabasePermission.ps1 new file mode 100644 index 000000000..8d43bcbfb --- /dev/null +++ b/source/Public/ConvertFrom-DatabasePermission.ps1 @@ -0,0 +1,47 @@ +<# + .SYNOPSIS + Converts a DatabasePermission object into an object of the type + Microsoft.SqlServer.Management.Smo.DatabasePermissionSet. + + .PARAMETER Permission + Specifies a DatabasePermission object. + + .OUTPUTS + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] + + .EXAMPLE + [DatabasePermission] @{ + State = 'Grant' + Permission = 'Connect' + } | ConvertFrom-DatabasePermission +#> +function ConvertFrom-DatabasePermission +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when the output type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [CmdletBinding()] + [OutputType([Microsoft.SqlServer.Management.Smo.DatabasePermissionSet])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [DatabasePermission] + $Permission + ) + + begin + { + $permissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + } + + process + { + foreach ($permissionName in $Permission.Permission) + { + $permissionSet.$permissionName = $true + } + } + + end + { + return $permissionSet + } +} diff --git a/source/Public/ConvertTo-DatabasePermission.ps1 b/source/Public/ConvertTo-DatabasePermission.ps1 index e0476ffd0..a21ad9dac 100644 --- a/source/Public/ConvertTo-DatabasePermission.ps1 +++ b/source/Public/ConvertTo-DatabasePermission.ps1 @@ -1,26 +1,19 @@ <# .SYNOPSIS - Returns the current permissions for the database principal. + Converts a collection of Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo + objects into an array of DatabasePermission objects. .PARAMETER DatabasePermissionInfo - Specifies current server connection object. - - .PARAMETER DatabaseName - Specifies the database name. - - .PARAMETER Name - Specifies the name of the database principal for which the permissions are - returned. + Specifies a collection of Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo + objects. .OUTPUTS - [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] + [DatabasePermission[]] .EXAMPLE $serverInstance = Connect-SqlDscDatabaseEngine $databasePermissionInfo = Get-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' ConvertTo-DatabasePermission -DatabasePermissionInfo $databasePermissionInfo - - .NOTES #> function ConvertTo-DatabasePermission { diff --git a/tests/Unit/Public/ConvertFrom-DatabasePermission.Tests.ps1 b/tests/Unit/Public/ConvertFrom-DatabasePermission.Tests.ps1 new file mode 100644 index 000000000..557b40d4a --- /dev/null +++ b/tests/Unit/Public/ConvertFrom-DatabasePermission.Tests.ps1 @@ -0,0 +1,78 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { + BeforeAll { + $mockPermission = InModuleScope -ScriptBlock { + [DatabasePermission] @{ + State = 'Grant' + Permission = @( + 'Connect' + 'Alter' + ) + } + } + } + + It 'Should return the correct values' { + $mockResult = ConvertFrom-DatabasePermission -Permission $mockPermission + + $mockResult.Connect | Should -BeTrue + $mockResult.Alter | Should -BeTrue + $mockResult.Update | Should -BeFalse + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockPermission | ConvertFrom-DatabasePermission + + $mockResult.Connect | Should -BeTrue + $mockResult.Alter | Should -BeTrue + $mockResult.Update | Should -BeFalse + } + } +} From e666a52497fc50966868e7cacb0bc0b49b597a68 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 26 Jul 2022 16:03:11 +0200 Subject: [PATCH 87/98] Rename public functions --- source/Classes/020.SqlDatabasePermission.ps1 | 7 ++- ... ConvertFrom-SqlDscDatabasePermission.ps1} | 4 +- ...=> ConvertTo-SqlDscDatabasePermission.ps1} | 4 +- ...rtFrom-SqlDscDatabasePermission.Tests.ps1} | 6 +-- ...vertTo-SqlDscDatabasePermission.Tests.ps1} | 46 +++++++++---------- 5 files changed, 33 insertions(+), 34 deletions(-) rename source/Public/{ConvertFrom-DatabasePermission.ps1 => ConvertFrom-SqlDscDatabasePermission.ps1} (93%) rename source/Public/{ConvertTo-DatabasePermission.ps1 => ConvertTo-SqlDscDatabasePermission.ps1} (96%) rename tests/Unit/Public/{ConvertFrom-DatabasePermission.Tests.ps1 => ConvertFrom-SqlDscDatabasePermission.Tests.ps1} (91%) rename tests/Unit/Public/{ConvertTo-DatabasePermission.Tests.ps1 => ConvertTo-SqlDscDatabasePermission.Tests.ps1} (93%) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 1b430dbe7..3a2b1cde6 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -309,8 +309,7 @@ class SqlDatabasePermission : ResourceBase # If permissions was returned, build the current permission array of [DatabasePermission]. if ($databasePermissionInfo) { - # TODO: Below code could be a command ConvertTo-DatabasePermission that returns an array of [DatabasePermission] - [DatabasePermission[]] $currentState.Permission = $databasePermissionInfo | ConvertTo-DatabasePermission + [DatabasePermission[]] $currentState.Permission = $databasePermissionInfo | ConvertTo-SqlDscDatabasePermission } # Always return all State; 'Grant', 'GrantWithGrant', and 'Deny'. @@ -540,7 +539,7 @@ class SqlDatabasePermission : ResourceBase { foreach ($currentStateToRevoke in $permissionsToRevoke) { - $revokePermissionSet = $currentStateToRevoke | ConvertFrom-DatabasePermission + $revokePermissionSet = $currentStateToRevoke | ConvertFrom-SqlDscDatabasePermission $setSqlDscDatabasePermissionParameters = @{ ServerObject = $serverObject @@ -586,7 +585,7 @@ class SqlDatabasePermission : ResourceBase # If there is not an empty array, change permissions. if (-not [System.String]::IsNullOrEmpty($currentDesiredPermissionState.Permission)) { - $permissionSet = $currentDesiredPermissionState | ConvertFrom-DatabasePermission + $permissionSet = $currentDesiredPermissionState | ConvertFrom-SqlDscDatabasePermission $setSqlDscDatabasePermissionParameters = @{ ServerObject = $serverObject diff --git a/source/Public/ConvertFrom-DatabasePermission.ps1 b/source/Public/ConvertFrom-SqlDscDatabasePermission.ps1 similarity index 93% rename from source/Public/ConvertFrom-DatabasePermission.ps1 rename to source/Public/ConvertFrom-SqlDscDatabasePermission.ps1 index 8d43bcbfb..99b4a2997 100644 --- a/source/Public/ConvertFrom-DatabasePermission.ps1 +++ b/source/Public/ConvertFrom-SqlDscDatabasePermission.ps1 @@ -13,9 +13,9 @@ [DatabasePermission] @{ State = 'Grant' Permission = 'Connect' - } | ConvertFrom-DatabasePermission + } | ConvertFrom-SqlDscDatabasePermission #> -function ConvertFrom-DatabasePermission +function ConvertFrom-SqlDscDatabasePermission { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when the output type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [CmdletBinding()] diff --git a/source/Public/ConvertTo-DatabasePermission.ps1 b/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 similarity index 96% rename from source/Public/ConvertTo-DatabasePermission.ps1 rename to source/Public/ConvertTo-SqlDscDatabasePermission.ps1 index a21ad9dac..d6bdccf60 100644 --- a/source/Public/ConvertTo-DatabasePermission.ps1 +++ b/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 @@ -13,9 +13,9 @@ .EXAMPLE $serverInstance = Connect-SqlDscDatabaseEngine $databasePermissionInfo = Get-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' - ConvertTo-DatabasePermission -DatabasePermissionInfo $databasePermissionInfo + ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $databasePermissionInfo #> -function ConvertTo-DatabasePermission +function ConvertTo-SqlDscDatabasePermission { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when the output type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [CmdletBinding()] diff --git a/tests/Unit/Public/ConvertFrom-DatabasePermission.Tests.ps1 b/tests/Unit/Public/ConvertFrom-SqlDscDatabasePermission.Tests.ps1 similarity index 91% rename from tests/Unit/Public/ConvertFrom-DatabasePermission.Tests.ps1 rename to tests/Unit/Public/ConvertFrom-SqlDscDatabasePermission.Tests.ps1 index 557b40d4a..32022359f 100644 --- a/tests/Unit/Public/ConvertFrom-DatabasePermission.Tests.ps1 +++ b/tests/Unit/Public/ConvertFrom-SqlDscDatabasePermission.Tests.ps1 @@ -45,7 +45,7 @@ AfterAll { Get-Module -Name $script:dscModuleName -All | Remove-Module -Force } -Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { +Describe 'ConvertFrom-SqlDscDatabasePermission' -Tag 'Public' { BeforeAll { $mockPermission = InModuleScope -ScriptBlock { [DatabasePermission] @{ @@ -59,7 +59,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { } It 'Should return the correct values' { - $mockResult = ConvertFrom-DatabasePermission -Permission $mockPermission + $mockResult = ConvertFrom-SqlDscDatabasePermission -Permission $mockPermission $mockResult.Connect | Should -BeTrue $mockResult.Alter | Should -BeTrue @@ -68,7 +68,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { Context 'When passing DatabasePermissionInfo over the pipeline' { It 'Should return the correct values' { - $mockResult = $mockPermission | ConvertFrom-DatabasePermission + $mockResult = $mockPermission | ConvertFrom-SqlDscDatabasePermission $mockResult.Connect | Should -BeTrue $mockResult.Alter | Should -BeTrue diff --git a/tests/Unit/Public/ConvertTo-DatabasePermission.Tests.ps1 b/tests/Unit/Public/ConvertTo-SqlDscDatabasePermission.Tests.ps1 similarity index 93% rename from tests/Unit/Public/ConvertTo-DatabasePermission.Tests.ps1 rename to tests/Unit/Public/ConvertTo-SqlDscDatabasePermission.Tests.ps1 index a82ad4890..7c3e3b3e5 100644 --- a/tests/Unit/Public/ConvertTo-DatabasePermission.Tests.ps1 +++ b/tests/Unit/Public/ConvertTo-SqlDscDatabasePermission.Tests.ps1 @@ -45,21 +45,21 @@ AfterAll { Get-Module -Name $script:dscModuleName -All | Remove-Module -Force } -Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { +Describe 'ConvertTo-SqlDscDatabasePermission' -Tag 'Public' { Context 'When passing empty collection as PermissionInfo' { BeforeAll { [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() } It 'Should return the correct values' { - $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection $mockResult | Should -HaveCount 0 } Context 'When passing DatabasePermissionInfo over the pipeline' { It 'Should return the correct values' { - $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission $mockResult | Should -HaveCount 0 } @@ -82,7 +82,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { } It 'Should return the correct values' { - $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection $mockResult | Should -HaveCount 1 @@ -92,7 +92,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { Context 'When passing DatabasePermissionInfo over the pipeline' { It 'Should return the correct values' { - $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission $mockResult | Should -HaveCount 1 @@ -126,7 +126,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { } It 'Should return the correct values' { - $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection $mockResult | Should -HaveCount 1 @@ -137,7 +137,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { Context 'When passing DatabasePermissionInfo over the pipeline' { It 'Should return the correct values' { - $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission $mockResult | Should -HaveCount 1 @@ -174,7 +174,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { } It 'Should return the correct values' { - $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection $mockResult | Should -HaveCount 1 @@ -187,7 +187,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { Context 'When passing DatabasePermissionInfo over the pipeline' { It 'Should return the correct values' { - $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission $mockResult | Should -HaveCount 1 @@ -217,7 +217,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { } It 'Should return the correct values' { - $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection $mockResult | Should -HaveCount 1 @@ -227,7 +227,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { Context 'When passing DatabasePermissionInfo over the pipeline' { It 'Should return the correct values' { - $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission $mockResult | Should -HaveCount 1 @@ -261,7 +261,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { } It 'Should return the correct values' { - $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection $mockResult | Should -HaveCount 1 @@ -272,7 +272,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { Context 'When passing DatabasePermissionInfo over the pipeline' { It 'Should return the correct values' { - $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission $mockResult | Should -HaveCount 1 @@ -309,7 +309,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { } It 'Should return the correct values' { - $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection $mockResult | Should -HaveCount 1 @@ -322,7 +322,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { Context 'When passing DatabasePermissionInfo over the pipeline' { It 'Should return the correct values' { - $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission $mockResult | Should -HaveCount 1 @@ -352,7 +352,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { } It 'Should return the correct values' { - $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection $mockResult | Should -HaveCount 1 @@ -362,7 +362,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { Context 'When passing DatabasePermissionInfo over the pipeline' { It 'Should return the correct values' { - $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission $mockResult | Should -HaveCount 1 @@ -396,7 +396,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { } It 'Should return the correct values' { - $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection $mockResult | Should -HaveCount 1 @@ -407,7 +407,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { Context 'When passing DatabasePermissionInfo over the pipeline' { It 'Should return the correct values' { - $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission $mockResult | Should -HaveCount 1 @@ -444,7 +444,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { } It 'Should return the correct values' { - $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection $mockResult | Should -HaveCount 1 @@ -457,7 +457,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { Context 'When passing DatabasePermissionInfo over the pipeline' { It 'Should return the correct values' { - $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission $mockResult | Should -HaveCount 1 @@ -508,7 +508,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { } It 'Should return the correct values' { - $mockResult = ConvertTo-DatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection $mockResult | Should -HaveCount 3 @@ -534,7 +534,7 @@ Describe 'ConvertTo-DatabasePermission' -Tag 'Public' { Context 'When passing DatabasePermissionInfo over the pipeline' { It 'Should return the correct values' { - $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-DatabasePermission + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission $mockResult | Should -HaveCount 3 From f0fda2c880e98d6c0bf034807b8b90bd0de39a54 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 26 Jul 2022 16:26:08 +0200 Subject: [PATCH 88/98] Cleanup SqlDatabasePermission --- CONTRIBUTING.md | 9 ++++-- source/Classes/020.SqlDatabasePermission.ps1 | 31 ++++++++------------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8d5de78fc..2d29d410c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -283,8 +283,13 @@ be compiled to a .mof file. If the tests find any errors the build will fail. #### Terminating Error A terminating error is an error that prevents the resource to continue further. -If a DSC resource shall throw an terminating error the statement `throw` shall -be used. +If a DSC resource shall throw an terminating error the commands of the module +**DscResource.Common** shall be used primarily; [`New-InvalidArgumentException`](https://github.com/dsccommunity/DscResource.Common#new-invalidargumentexception), +[`New-InvalidDataExcpetion`](https://github.com/dsccommunity/DscResource.Common#new-invaliddataexception), +[`New-InvalidOperationException`](https://github.com/dsccommunity/DscResource.Common#new-invalidoperationexception), +[`New-InvalidResultException`](https://github.com/dsccommunity/DscResource.Common#new-invalidresultexception), +or [`New-NotImplementedException`](https://github.com/dsccommunity/DscResource.Common#new-notimplementedexception). +If neither of those commands works in the scenarion then `throw` shall be used. ### Commands diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 3a2b1cde6..fe5bcb089 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -472,8 +472,7 @@ class SqlDatabasePermission : ResourceBase Evaluate if there are any permissions that should be revoked from the current state. #> - # TODO: replace all $this.Permission* with $properties.Permission* - foreach ($currentDesiredPermissionState in $this.Permission) + foreach ($currentDesiredPermissionState in $properties.Permission) { $currentPermissionsForState = $currentState.Permission | Where-Object -FilterScript { @@ -511,7 +510,7 @@ class SqlDatabasePermission : ResourceBase in the current state. Grant or Deny all permission assigned to the property Permission regardless if they were already present or not. #> - [DatabasePermission[]] $permissionsToGrantOrDeny = $this.Permission + [DatabasePermission[]] $permissionsToGrantOrDeny = $properties.Permission } if ($properties.ContainsKey('PermissionToExclude')) @@ -521,7 +520,7 @@ class SqlDatabasePermission : ResourceBase all permission assigned to the property PermissionToExclude regardless if they were already revoked or not. #> - [DatabasePermission[]] $permissionsToRevoke = $this.PermissionToExclude + [DatabasePermission[]] $permissionsToRevoke = $properties.PermissionToExclude } if ($properties.ContainsKey('PermissionToInclude')) @@ -531,7 +530,7 @@ class SqlDatabasePermission : ResourceBase in the current state. Grant or Deny all permission assigned to the property Permission regardless if they were already present or not. #> - [DatabasePermission[]] $permissionsToGrantOrDeny = $this.PermissionToInclude + [DatabasePermission[]] $permissionsToGrantOrDeny = $properties.PermissionToInclude } # Revoke all the permissions set in $permissionsToRevoke @@ -566,13 +565,6 @@ class SqlDatabasePermission : ResourceBase $this.DatabaseName ) - <# - TODO: Update the CONTRIBUTING.md section "Class-based DSC resource" - that now says that 'throw' should be used.. we should use - helper function instead. Or something similar to commands - where the ID number is part of code? But might be a problem - tracing a specific verbose string down? - #> New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } } @@ -660,8 +652,9 @@ class SqlDatabasePermission : ResourceBase # Must include either of the permission properties. if ([System.String]::IsNullOrEmpty($assignedPermissionProperty)) { - # TODO: Should throw ArgumentException - throw $this.localizedData.MustAssignOnePermissionProperty + $errorMessage = $script:localizedData.MustAssignOnePermissionProperty + + New-InvalidArgumentException -ArgumentName 'Permission, PermissionToInclude, PermissionToExclude' -Message $errorMessage } # One State cannot exist several times in the same resource instance. @@ -675,8 +668,9 @@ class SqlDatabasePermission : ResourceBase if ($permissionStateGroupCount -gt 1) { - # TODO: Should throw ArgumentException - throw $this.localizedData.DuplicatePermissionState + $errorMessage = $script:localizedData.DuplicatePermissionState + + New-InvalidArgumentException -ArgumentName $currentAssignedPermissionProperty -Message $errorMessage } } @@ -691,8 +685,9 @@ class SqlDatabasePermission : ResourceBase if ($missingPermissionState) { - # TODO: Should throw ArgumentException - throw $this.localizedData.MissingPermissionState + $errorMessage = $script:localizedData.MissingPermissionState + + New-InvalidArgumentException -ArgumentName 'Permission' -Message $errorMessage } } } From 357b0c32cf1487c8638537cc85dee009b9aaaea6 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 27 Jul 2022 10:43:39 +0200 Subject: [PATCH 89/98] Cleanup SqlDatabasePermission --- source/Classes/020.SqlDatabasePermission.ps1 | 40 +++++++++-- .../en-US/SqlDatabasePermission.strings.psd1 | 2 + .../Classes/SqlDatabasePermission.Tests.ps1 | 72 +++++++++++++++++++ 3 files changed, 108 insertions(+), 6 deletions(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index fe5bcb089..66ab3ac5b 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -622,10 +622,6 @@ class SqlDatabasePermission : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { - # TODO: Add the evaluation so that one permission can't be added two different states ('Grant' and 'Deny') in the same resource instance. - - # TODO: PermissionToInclude and PermissionToExclude must not contain an empty collection for property Permission - # PermissionToInclude and PermissionToExclude should be mutually exclusive from Permission $assertBoundParameterParameters = @{ BoundParameterList = $properties @@ -657,9 +653,9 @@ class SqlDatabasePermission : ResourceBase New-InvalidArgumentException -ArgumentName 'Permission, PermissionToInclude, PermissionToExclude' -Message $errorMessage } - # One State cannot exist several times in the same resource instance. foreach ($currentAssignedPermissionProperty in $assignedPermissionProperty) { + # One State cannot exist several times in the same resource instance. $permissionStateGroupCount = @( $properties.$currentAssignedPermissionProperty | Group-Object -NoElement -Property 'State' -CaseSensitive:$false | @@ -672,9 +668,21 @@ class SqlDatabasePermission : ResourceBase New-InvalidArgumentException -ArgumentName $currentAssignedPermissionProperty -Message $errorMessage } + + # A specific permission must only exist in one permission state. + $permissionGroupCount = $properties.$currentAssignedPermissionProperty.Permission | + Group-Object -NoElement -CaseSensitive:$false | + Select-Object -ExpandProperty 'Count' + + if ($permissionGroupCount -gt 1) + { + $errorMessage = $script:localizedData.DuplicatePermissionBetweenState + + New-InvalidArgumentException -ArgumentName $currentAssignedPermissionProperty -Message $errorMessage + } } - if ($assignedPermissionProperty -contains 'Permission') + if ($properties.Keys -contains 'Permission') { # Each State must exist once. $missingPermissionState = ( @@ -690,5 +698,25 @@ class SqlDatabasePermission : ResourceBase New-InvalidArgumentException -ArgumentName 'Permission' -Message $errorMessage } } + + <# + Each permission state in the properties PermissionToInclude and PermissionToExclude + must have specified at minimum one permission. + #> + foreach ($currentAssignedPermissionProperty in @('PermissionToInclude', 'PermissionToExclude')) + { + if ($properties.Keys -contains $currentAssignedPermissionProperty) + { + foreach ($currentDatabasePermission in $properties.$currentAssignedPermissionProperty) + { + if ($currentDatabasePermission.Permission.Count -eq 0) + { + $errorMessage = $script:localizedData.MustHaveMinimumOnePermissionInState -f $currentAssignedPermissionProperty + + New-InvalidArgumentException -ArgumentName $currentAssignedPermissionProperty -Message $errorMessage + } + } + } + } } } diff --git a/source/en-US/SqlDatabasePermission.strings.psd1 b/source/en-US/SqlDatabasePermission.strings.psd1 index 03ace852a..f2cb010d5 100644 --- a/source/en-US/SqlDatabasePermission.strings.psd1 +++ b/source/en-US/SqlDatabasePermission.strings.psd1 @@ -18,4 +18,6 @@ ConvertFrom-StringData @' DuplicatePermissionState = One or more permission states was added more than once. It is only allowed to specify one of each permission state. (SDP0009) MissingPermissionState = One or more permission states was missing. One of each permission state must be provided. (SDP0009) MustAssignOnePermissionProperty = At least one of the properties 'Permission', 'PermissionToInclude', or 'PermissionToExclude' must be specified. (SDP0010) + DuplicatePermissionBetweenState = One or more permission state specifies the same permission. It is only allowed to specify a specific permission in one permission state. (SDP0011) + MustHaveMinimumOnePermissionInState = At least one state does not specify a permission in the property '{0}'. '@ diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index 5260d3045..446aa2b61 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -1790,4 +1790,76 @@ Describe 'SqlDatabasePermission\AssertProperties()' -Tag 'AssertProperties' { } } } + + Context 'When a permission Property contain the same permission name twice' { + It 'Should throw the correct error for property ' -ForEach @( + @{ + MockPropertyName = 'Permission' + } + @{ + MockPropertyName = 'PermissionToInclude' + } + @{ + MockPropertyName = 'PermissionToExclude' + } + ) { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.DuplicatePermissionBetweenState + } + + InModuleScope -Parameters $_ -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{ + $MockPropertyName = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = 'Select' + } + [DatabasePermission] @{ + State = 'Deny' + Permission = 'Select' + } + ) + }) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When a permission Property does not specify any permission name' { + It 'Should throw the correct error for property ' -ForEach @( + @{ + MockPropertyName = 'PermissionToInclude' + } + @{ + MockPropertyName = 'PermissionToExclude' + } + ) { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.MustHaveMinimumOnePermissionInState + } + + InModuleScope -Parameters $_ -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{ + $MockPropertyName = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + <# + This should not be able to be $null since the property + is mandatory but do allow empty collection. So no need + to test using $null value. + #> + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = 'Select' + } + ) + }) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } } From b37aa7cfb9d8200de31bc6c19d8a01548a50ef6c Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 27 Jul 2022 12:52:06 +0200 Subject: [PATCH 90/98] Cleanup ResourceBase --- source/Classes/010.ResourceBase.ps1 | 88 +++++++++-------- source/Private/Get-DesiredStateProperty.ps1 | 44 --------- source/Private/Get-DscProperty.ps1 | 4 +- source/Private/Get-KeyProperty.ps1 | 47 --------- .../Test-ResourcePropertyIsAssigned.ps1 | 2 +- tests/Unit/Classes/ResourceBase.Tests.ps1 | 17 ++-- .../Get-DesiredStateProperty.Tests.ps1 | 97 ------------------ tests/Unit/Private/Get-KeyProperty.Tests.ps1 | 98 ------------------- 8 files changed, 55 insertions(+), 342 deletions(-) delete mode 100644 source/Private/Get-DesiredStateProperty.ps1 delete mode 100644 source/Private/Get-KeyProperty.ps1 delete mode 100644 tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 delete mode 100644 tests/Unit/Private/Get-KeyProperty.Tests.ps1 diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index af8616f2a..c5f561712 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -30,23 +30,11 @@ class ResourceBase { $this.Assert() - # TODO: Use: Get-DscProperty -Type 'Key' # Get all key properties. - $keyProperty = $this | Get-KeyProperty + $keyProperty = $this | Get-DscProperty -Type 'Key' Write-Verbose -Message ($this.localizedData.GetCurrentState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) - <# - TODO: Should call back to the derived class for proper handling of adding - additional parameters to the variable $keyProperty that needs to be - passed to GetCurrentState(). - - Second though, might not be necessary as the override for GetCurrentState - can call $this. to get any non-key properties. - It might even be that we don't need Get-KeyProperty? - #> - #$specialKeyProperty = @() - $getCurrentStateResult = $this.GetCurrentState($keyProperty) $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) @@ -67,20 +55,6 @@ class ResourceBase { # Add the key value to the instance to be returned. $dscResourceObject.$propertyName = $this.$propertyName - - <# - If the key property should be enforced, add it to the current - state hashtable so Compare() will enforce it. The property will - always be in desired state since it is the desired state value - that is set as the current state value. But this will help so - that a derived class' method GetCurrentState() does not need - to return the key property values if the properties has not - been added to the class property '$this.notEnforcedProperties'. - #> - if ($propertyName -notin $this.notEnforcedProperties) - { - $getCurrentStateResult.$propertyName = $this.$propertyName - } } } @@ -111,18 +85,41 @@ class ResourceBase #> if (($this | Test-ResourceHasProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) { - # TODO: This should evaluate if the key properties is in desired state, if so set Present, otherwise set Absent - - if (($propertiesNotInDesiredState -and $this.Ensure -eq [Ensure]::Present) -or (-not $propertiesNotInDesiredState -and $this.Ensure -eq [Ensure]::Absent)) - { - $dscResourceObject.Ensure = [Ensure]::Absent - } - elseif ($propertiesNotInDesiredState -and $this.Ensure -eq [Ensure]::Absent) + if ($propertiesNotInDesiredState) { - $dscResourceObject.Ensure = [Ensure]::Present + <# + Get all the key properties that might not be in desired state. + This will return $null if all key properties are in desired state. + #> + $keyPropertiesNotInDesiredState = $this | Get-DscProperty -Name $propertiesNotInDesiredState.Property -Type 'Key' + + if ($keyPropertiesNotInDesiredState) + { + <# + The compare come back with at least one key property that was + not in desired state. That only happens if the object does not + exist on the node, so the Ensure value is set to Absent since + the object does not exist. + #> + $dscResourceObject.Ensure = [Ensure]::Absent + } + else + { + <# + The compare come back with all key properties in desired state. + That only happens if the object exist on the node, so the Ensure + value is set to Present since the object exist. + #> + $dscResourceObject.Ensure = [Ensure]::Present + } } else { + <# + The compare come back with $null, meaning that all key properties + match. That only happens if the object exist on the node, so the + Ensure value is set to Present since the object exist. + #> $dscResourceObject.Ensure = [Ensure]::Present } } @@ -182,7 +179,10 @@ class ResourceBase [void] Set() { - Write-Verbose -Message ($this.localizedData.SetDesiredState -f $this.GetType().Name, ($this | Get-KeyProperty | ConvertTo-Json -Compress)) + # Get all key properties. + $keyProperty = $this | Get-DscProperty -Type 'Key' + + Write-Verbose -Message ($this.localizedData.SetDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) $this.Assert() @@ -215,7 +215,10 @@ class ResourceBase [System.Boolean] Test() { - Write-Verbose -Message ($this.localizedData.TestDesiredState -f $this.GetType().Name, ($this | Get-KeyProperty | ConvertTo-Json -Compress)) + # Get all key properties. + $keyProperty = $this | Get-DscProperty -Type 'Key' + + Write-Verbose -Message ($this.localizedData.TestDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) $this.Assert() @@ -253,8 +256,8 @@ class ResourceBase #> hidden [System.Collections.Hashtable[]] Compare() { - # TODO: Replace ConvertFrom-DscResourceInstance with Get-DscProperty? - $currentState = $this.Get() | ConvertFrom-DscResourceInstance + # Get the current state, all properties except Read properties . + $currentState = $this.Get() | Get-DscProperty -Type @('Key', 'Mandatory', 'Optional') return $this.Compare($currentState, @()) } @@ -268,8 +271,8 @@ class ResourceBase #> hidden [System.Collections.Hashtable[]] Compare([System.Collections.Hashtable] $currentState, [System.String[]] $excludeProperties) { - # TODO: Replace this with Get-DscProperty - $desiredState = $this | Get-DesiredStateProperty + # Get the desired state, all assigned properties that has an non-null value. + $desiredState = $this | Get-DscProperty -Type @('Key', 'Mandatory', 'Optional') -HasValue $CompareDscParameterState = @{ CurrentValues = $currentState @@ -291,9 +294,8 @@ class ResourceBase # This method should normally not be overridden. hidden [void] Assert() { - # TODO: Replace this with Get-DscProperty # Get the properties that has a non-null value and is not of type Read. - $desiredState = $this | Get-DesiredStateProperty + $desiredState = $this | Get-DscProperty -Type @('Key', 'Mandatory', 'Optional') -HasValue $this.AssertProperties($desiredState) } diff --git a/source/Private/Get-DesiredStateProperty.ps1 b/source/Private/Get-DesiredStateProperty.ps1 deleted file mode 100644 index ab7a0f431..000000000 --- a/source/Private/Get-DesiredStateProperty.ps1 +++ /dev/null @@ -1,44 +0,0 @@ - -<# - .SYNOPSIS - Returns the properties that should be enforced for the desired state. - - .DESCRIPTION - Returns the properties that should be enforced for the desired state. - This function converts a PSObject into a hashtable containing the properties - that should be enforced. - - .PARAMETER InputObject - The object that contain the properties with the desired state. - - .OUTPUTS - Hashtable -#> -function Get-DesiredStateProperty -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [PSObject] - $InputObject - ) - - $desiredStateProperty = $InputObject | ConvertFrom-DscResourceInstance - - <# - Remove properties that have $null as the value, and remove read - properties so that there is no chance to compare those. - #> - @($desiredStateProperty.Keys) | ForEach-Object -Process { - $isReadProperty = $InputObject.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'NotConfigurable' }).NamedArguments.TypedValue.Value -eq $true - - if ($isReadProperty -or $null -eq $desiredStateProperty[$_]) - { - $desiredStateProperty.Remove($_) - } - } - - return $desiredStateProperty -} diff --git a/source/Private/Get-DscProperty.ps1 b/source/Private/Get-DscProperty.ps1 index bc0ecb124..305df0f1d 100644 --- a/source/Private/Get-DscProperty.ps1 +++ b/source/Private/Get-DscProperty.ps1 @@ -36,9 +36,7 @@ function Get-DscProperty $HasValue ) - $property = $InputObject | - Get-Member -MemberType 'Property' | - Select-Object -ExpandProperty 'Name' | + $property = $InputObject.PSObject.Properties.Name | Where-Object -FilterScript { <# Return all properties if $Name is not assigned, or if assigned diff --git a/source/Private/Get-KeyProperty.ps1 b/source/Private/Get-KeyProperty.ps1 deleted file mode 100644 index ef8d1ff59..000000000 --- a/source/Private/Get-KeyProperty.ps1 +++ /dev/null @@ -1,47 +0,0 @@ - -<# - .SYNOPSIS - Returns all of the DSC resource key properties and their values. - - .DESCRIPTION - Returns all of the DSC resource key properties and their values. - - .PARAMETER InputObject - The object that contain one or more key properties. - - .OUTPUTS - Hashtable -#> -function Get-KeyProperty -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [PSObject] - $InputObject - ) - - # Get all key properties. - $keyProperty = $InputObject | - Get-Member -MemberType 'Property' | - Select-Object -ExpandProperty 'Name' | - Where-Object -FilterScript { - $InputObject.GetType().GetMember($_).CustomAttributes.Where( - { - $_.AttributeType.Name -eq 'DscPropertyAttribute' -and - $_.NamedArguments.MemberName -eq 'Key' - } - ).NamedArguments.TypedValue.Value -eq $true - } - - # Return a hashtable containing each key property and its value. - $getKeyPropertyResult = @{} - - $keyProperty | ForEach-Object -Process { - $getKeyPropertyResult.$_ = $InputObject.$_ - } - - return $getKeyPropertyResult -} diff --git a/source/Private/Test-ResourcePropertyIsAssigned.ps1 b/source/Private/Test-ResourcePropertyIsAssigned.ps1 index daddca646..9f59262ff 100644 --- a/source/Private/Test-ResourcePropertyIsAssigned.ps1 +++ b/source/Private/Test-ResourcePropertyIsAssigned.ps1 @@ -29,7 +29,7 @@ function Test-ResourcePropertyIsAssigned $Name ) - $isAssigned = -not $null -eq $InputObject.$Name + $isAssigned = -not ($null -eq $InputObject.$Name) return $isAssigned } diff --git a/tests/Unit/Classes/ResourceBase.Tests.ps1 b/tests/Unit/Classes/ResourceBase.Tests.ps1 index 2940774e3..9a3379816 100644 --- a/tests/Unit/Classes/ResourceBase.Tests.ps1 +++ b/tests/Unit/Classes/ResourceBase.Tests.ps1 @@ -231,8 +231,8 @@ class MyMockResource : ResourceBase [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { - # This does not return the key property, to let base class handle that. return @{ + MyResourceKeyProperty1 = $null MyResourceProperty2 = $null } } @@ -258,10 +258,12 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() $getResult = $mockResourceBaseInstance.Get() - $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceKeyProperty1 | Should -BeNullOrEmpty $getResult.MyResourceProperty2 | Should -BeNullOrEmpty $getResult.Ensure | Should -Be ([Ensure]::Absent) - $getResult.Reasons | Should -BeNullOrEmpty + + $getResult.Reasons | Should -HaveCount 1 + $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceKeyProperty1' } } } @@ -613,7 +615,6 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() InModuleScope -ScriptBlock { $mockResourceBaseInstance.Ensure = [Ensure]::Absent $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' - $mockResourceBaseInstance.MyResourceProperty2 = $null $getResult = $mockResourceBaseInstance.Get() @@ -621,9 +622,7 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() $getResult.MyResourceProperty2 | Should -Be 'MyValue2' $getResult.Ensure | Should -Be ([Ensure]::Present) - $getResult.Reasons | Should -HaveCount 1 - $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceProperty2' - $getResult.Reasons[0].Phrase | Should -Be 'The property MyResourceProperty2 should be "", but was "MyValue2"' + $getResult.Reasons | Should -BeNullOrEmpty } } } @@ -647,7 +646,7 @@ class MyMockResource : ResourceBase [DscProperty()] [Ensure] - $Ensure = [Ensure]::Present + $Ensure = ([Ensure]::Present) [DscProperty()] [System.String] @@ -660,7 +659,7 @@ class MyMockResource : ResourceBase [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { return @{ - Ensure = [Ensure]::Absent + Ensure = ([Ensure]::Absent) MyResourceKeyProperty1 = 'MyValue1' MyResourceProperty2 = 'MyValue2' } diff --git a/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 b/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 deleted file mode 100644 index 663a47416..000000000 --- a/tests/Unit/Private/Get-DesiredStateProperty.Tests.ps1 +++ /dev/null @@ -1,97 +0,0 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] -param () - -BeforeDiscovery { - try - { - if (-not (Get-Module -Name 'DscResource.Test')) - { - # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. - if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) - { - # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null - } - - # If the dependencies has not been resolved, this will throw an error. - Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' - } - } - catch [System.IO.FileNotFoundException] - { - throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' - } -} - -BeforeAll { - $script:dscModuleName = 'SqlServerDsc' - - Import-Module -Name $script:dscModuleName - - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName - $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName - $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName -} - -AfterAll { - $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') - $PSDefaultParameterValues.Remove('Mock:ModuleName') - $PSDefaultParameterValues.Remove('Should:ModuleName') - - # Unload the module being tested so that it doesn't impact any other tests. - Get-Module -Name $script:dscModuleName -All | Remove-Module -Force -} - -Describe 'Get-DesiredStateProperty' -Tag 'Private' { - BeforeAll { - <# - Must use a here-string because we need to pass 'using' which must be - first in a scriptblock, but if it is outside the here-string then - PowerShell will fail to parse the test script. - #> - $inModuleScopeScriptBlock = @' -class MyMockResource -{ - [DscProperty(Key)] - [System.String] - $MyResourceKeyProperty1 - - [DscProperty()] - [System.String] - $MyResourceProperty2 - - [DscProperty()] - [System.String] - $MyResourceProperty3 - - [DscProperty(NotConfigurable)] - [System.String] - $MyResourceReadProperty -} - -$script:mockResourceBaseInstance = [MyMockResource]::new() -$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' -$script:mockResourceBaseInstance.MyResourceProperty3 = 'MockValue3' -$script:mockResourceBaseInstance.MyResourceReadProperty = 'MockReadValue1' -'@ - - InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) - } - - It 'Should return the correct value' { - InModuleScope -ScriptBlock { - $result = Get-DesiredStateProperty -InputObject $script:mockResourceBaseInstance - - $result | Should -BeOfType [System.Collections.Hashtable] - - $result.Keys | Should -Not -Contain 'MyResourceProperty2' -Because 'properties with $null values should not be part of the collection' - $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection even if they have values' - - $result.Keys | Should -Contain 'MyResourceKeyProperty1' -Because 'the property was set to a value in the mocked class' - $result.Keys | Should -Contain 'MyResourceProperty3' -Because 'the property was set to a value in the mocked class' - - $result.MyResourceKeyProperty1 | Should -Be 'MockValue1' - $result.MyResourceProperty3 | Should -Be 'MockValue3' - } - } -} diff --git a/tests/Unit/Private/Get-KeyProperty.Tests.ps1 b/tests/Unit/Private/Get-KeyProperty.Tests.ps1 deleted file mode 100644 index eec799e1b..000000000 --- a/tests/Unit/Private/Get-KeyProperty.Tests.ps1 +++ /dev/null @@ -1,98 +0,0 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] -param () - -BeforeDiscovery { - try - { - if (-not (Get-Module -Name 'DscResource.Test')) - { - # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. - if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) - { - # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null - } - - # If the dependencies has not been resolved, this will throw an error. - Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' - } - } - catch [System.IO.FileNotFoundException] - { - throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' - } -} - -BeforeAll { - $script:dscModuleName = 'SqlServerDsc' - - Import-Module -Name $script:dscModuleName - - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName - $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName - $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName -} - -AfterAll { - $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') - $PSDefaultParameterValues.Remove('Mock:ModuleName') - $PSDefaultParameterValues.Remove('Should:ModuleName') - - # Unload the module being tested so that it doesn't impact any other tests. - Get-Module -Name $script:dscModuleName -All | Remove-Module -Force -} - -Describe 'Get-KeyProperty' -Tag 'Private' { - BeforeAll { - <# - Must use a here-string because we need to pass 'using' which must be - first in a scriptblock, but if it is outside the here-string then - PowerShell will fail to parse the test script. - #> - $inModuleScopeScriptBlock = @' -class MyMockResource -{ - [DscProperty(Key)] - [System.String] - $MyResourceKeyProperty1 - - [DscProperty(Key)] - [System.String] - $MyResourceKeyProperty2 - - [DscProperty()] - [System.String] - $MyResourceProperty3 - - [DscProperty(NotConfigurable)] - [System.String] - $MyResourceReadProperty -} - -$script:mockResourceBaseInstance = [MyMockResource]::new() -$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' -$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' -$script:mockResourceBaseInstance.MyResourceProperty3 = 'MockValue3' -$script:mockResourceBaseInstance.MyResourceReadProperty = 'MockReadValue1' -'@ - - InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) - } - - It 'Should return the correct value' { - InModuleScope -ScriptBlock { - $result = Get-KeyProperty -InputObject $script:mockResourceBaseInstance - - $result | Should -BeOfType [System.Collections.Hashtable] - - $result.Keys | Should -Not -Contain 'MyResourceProperty3' -Because 'non-key properties should not be part of the collection' - $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' - - $result.Keys | Should -Contain 'MyResourceKeyProperty1' -Because 'the property is a key property' - $result.Keys | Should -Contain 'MyResourceKeyProperty2' -Because 'the property is a key property' - - $result.MyResourceKeyProperty1 | Should -Be 'MockValue1' - $result.MyResourceKeyProperty2 | Should -Be 'MockValue2' - } - } -} From ebb69beadbead9faeb51392da50b138b39be0725 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 27 Jul 2022 12:57:14 +0200 Subject: [PATCH 91/98] Fix localized strings in SqlDatabasePermission --- source/Classes/020.SqlDatabasePermission.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 66ab3ac5b..46c49a827 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -648,7 +648,7 @@ class SqlDatabasePermission : ResourceBase # Must include either of the permission properties. if ([System.String]::IsNullOrEmpty($assignedPermissionProperty)) { - $errorMessage = $script:localizedData.MustAssignOnePermissionProperty + $errorMessage = $this.localizedData.MustAssignOnePermissionProperty New-InvalidArgumentException -ArgumentName 'Permission, PermissionToInclude, PermissionToExclude' -Message $errorMessage } @@ -664,7 +664,7 @@ class SqlDatabasePermission : ResourceBase if ($permissionStateGroupCount -gt 1) { - $errorMessage = $script:localizedData.DuplicatePermissionState + $errorMessage = $this.localizedData.DuplicatePermissionState New-InvalidArgumentException -ArgumentName $currentAssignedPermissionProperty -Message $errorMessage } @@ -676,7 +676,7 @@ class SqlDatabasePermission : ResourceBase if ($permissionGroupCount -gt 1) { - $errorMessage = $script:localizedData.DuplicatePermissionBetweenState + $errorMessage = $this.localizedData.DuplicatePermissionBetweenState New-InvalidArgumentException -ArgumentName $currentAssignedPermissionProperty -Message $errorMessage } @@ -693,7 +693,7 @@ class SqlDatabasePermission : ResourceBase if ($missingPermissionState) { - $errorMessage = $script:localizedData.MissingPermissionState + $errorMessage = $this.localizedData.MissingPermissionState New-InvalidArgumentException -ArgumentName 'Permission' -Message $errorMessage } @@ -711,7 +711,7 @@ class SqlDatabasePermission : ResourceBase { if ($currentDatabasePermission.Permission.Count -eq 0) { - $errorMessage = $script:localizedData.MustHaveMinimumOnePermissionInState -f $currentAssignedPermissionProperty + $errorMessage = $this.localizedData.MustHaveMinimumOnePermissionInState -f $currentAssignedPermissionProperty New-InvalidArgumentException -ArgumentName $currentAssignedPermissionProperty -Message $errorMessage } From 8dc83918dacc04f0d94e8b5ed4abf4e1fc29c907 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 27 Jul 2022 15:32:34 +0200 Subject: [PATCH 92/98] Add private function ConvertTo-Reason --- source/Classes/010.ResourceBase.ps1 | 52 ++----- source/Private/ConvertTo-Reason.ps1 | 79 +++++++++++ .../Classes/SqlDatabasePermission.Tests.ps1 | 2 +- .../ConvertFrom-CompareResult.Tests.ps1 | 28 ++-- tests/Unit/Private/ConvertTo-Reason.Tests.ps1 | 127 ++++++++++++++++++ 5 files changed, 232 insertions(+), 56 deletions(-) create mode 100644 source/Private/ConvertTo-Reason.ps1 create mode 100644 tests/Unit/Private/ConvertTo-Reason.Tests.ps1 diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index c5f561712..5db649f0a 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -22,7 +22,11 @@ class ResourceBase # Default constructor ResourceBase() { - # TODO: When this fails the LCM returns 'Failed to create an object of PowerShell class SqlDatabasePermission' instead of the actual error that occurred. + <# + TODO: When this fails, for example when the localized string file is missing + the LCM returns the error 'Failed to create an object of PowerShell + class SqlDatabasePermission' instead of the actual error that occurred. + #> $this.localizedData = Get-LocalizedDataRecursive -ClassName ($this | Get-ClassName -Recurse) } @@ -61,6 +65,10 @@ class ResourceBase $ignoreProperty = @() <# + TODO: This need to be re-evaluated for a resource that is using Ensure + property. How Ensure is handled might need to be refactored, or + removed altogether from this base class. + If the derived DSC resource has a Ensure property and it was not returned by GetCurrentState(), then the property Ensure is removed from the comparison (when calling Compare()). The property Ensure is ignored @@ -130,47 +138,9 @@ class ResourceBase #> if (($this | Test-ResourceHasProperty -Name 'Reasons') -and -not $getCurrentStateResult.ContainsKey('Reasons')) { - # TODO: Below should be a private function ConvertTo-Reason. - # Always return an empty array if all properties are in desired state. - $dscResourceObject.Reasons = [Reason[]] @() - - if ($propertiesNotInDesiredState) - { - foreach ($property in $propertiesNotInDesiredState) - { - if ($property.ExpectedValue -is [System.Enum]) - { - # TODO: Maybe we just convert the advanced types to JSON and do not convert other types? - # Test that on SqlDatabasePermission - - # Return the string representation of the value (instead of the numeric value). - $propertyExpectedValue = $property.ExpectedValue.ToString() - } - else - { - $propertyExpectedValue = $property.ExpectedValue - } - - if ($property.ActualValue -is [System.Enum]) - { - # TODO: Maybe we just convert the advanced types to JSON and do not convert other types? - # Test that on SqlDatabasePermission - - # Return the string representation of the value so that conversion to json is correct. - $propertyActualValue = $property.ActualValue.ToString() - } - else - { - $propertyActualValue = $property.ActualValue - } - - $dscResourceObject.Reasons += [Reason] @{ - Code = '{0}:{0}:{1}' -f $this.GetType(), $property.Property - Phrase = 'The property {0} should be {1}, but was {2}' -f $property.Property, ($propertyExpectedValue | ConvertTo-Json -Compress), ($propertyActualValue | ConvertTo-Json -Compress) - } - } - } + $dscResourceObject.Reasons = $propertiesNotInDesiredState | + ConvertTo-Reason -ResourceName $this.GetType() } # Return properties. diff --git a/source/Private/ConvertTo-Reason.ps1 b/source/Private/ConvertTo-Reason.ps1 new file mode 100644 index 000000000..dca746de4 --- /dev/null +++ b/source/Private/ConvertTo-Reason.ps1 @@ -0,0 +1,79 @@ +<# + .SYNOPSIS + Returns a array of the type `[Reason]`. + + .DESCRIPTION + This command converts the array of properties that is returned by the command + `Compare-DscParameterState`. The result is an array of the type `[Reason]` that + can be returned in a DSC resource's property **Reasons**. + + .PARAMETER Property + The result from the command Compare-DscParameterState. + + .EXAMPLE + ConvertTo-Reason -Property (Compare-DscParameterState) + + .OUTPUTS + [Reason[]] +#> +function ConvertTo-Reason +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when the output type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [CmdletBinding()] + [OutputType([Reason[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [AllowEmptyCollection()] + [AllowNull()] + [System.Collections.Hashtable[]] + $Property, + + [Parameter(Mandatory = $true)] + [System.String] + $ResourceName + ) + + begin + { + # Always return an empty array if there are no properties to add. + $reasons = [Reason[]] @() + } + + process + { + foreach ($currentProperty in $Property) + { + if ($currentProperty.ExpectedValue -is [System.Enum]) + { + # Return the string representation of the value (instead of the numeric value). + $propertyExpectedValue = $currentProperty.ExpectedValue.ToString() + } + else + { + $propertyExpectedValue = $currentProperty.ExpectedValue + } + + if ($property.ActualValue -is [System.Enum]) + { + # Return the string representation of the value so that conversion to json is correct. + $propertyActualValue = $currentProperty.ActualValue.ToString() + } + else + { + $propertyActualValue = $currentProperty.ActualValue + } + + $reasons += [Reason] @{ + Code = '{0}:{0}:{1}' -f $ResourceName, $currentProperty.Property + # Convert the object to JSON to handle complex types. + Phrase = 'The property {0} should be {1}, but was {2}' -f $currentProperty.Property, ($propertyExpectedValue | ConvertTo-Json -Compress), ($propertyActualValue | ConvertTo-Json -Compress) + } + } + } + + end + { + return $reasons + } +} diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index 446aa2b61..673f39107 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -234,7 +234,7 @@ Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { $currentState.Permission[0].State | Should -Be 'Grant' $currentState.Permission[0].Permission | Should -Be 'Connect' - $currentState.Reasons | Should -HaveCount 0 + $currentState.Reasons | Should -BeNullOrEmpty } } } diff --git a/tests/Unit/Private/ConvertFrom-CompareResult.Tests.ps1 b/tests/Unit/Private/ConvertFrom-CompareResult.Tests.ps1 index b8412d846..f086e3846 100644 --- a/tests/Unit/Private/ConvertFrom-CompareResult.Tests.ps1 +++ b/tests/Unit/Private/ConvertFrom-CompareResult.Tests.ps1 @@ -59,16 +59,16 @@ Describe 'ConvertFrom-CompareResult' -Tag 'Private' { } ) - $getDesiredStateForSplattingResult = ConvertFrom-CompareResult -CompareResult $mockProperties + $result = ConvertFrom-CompareResult -CompareResult $mockProperties - $getDesiredStateForSplattingResult | Should -BeOfType [System.Collections.Hashtable] + $result | Should -BeOfType [System.Collections.Hashtable] - $getDesiredStateForSplattingResult.Keys | Should -HaveCount 2 - $getDesiredStateForSplattingResult.Keys | Should -Contain 'MyResourceProperty1' - $getDesiredStateForSplattingResult.Keys | Should -Contain 'MyResourceProperty2' + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'MyResourceProperty1' + $result.Keys | Should -Contain 'MyResourceProperty2' - $getDesiredStateForSplattingResult.MyResourceProperty1 | Should -Be 'MyNewValue1' - $getDesiredStateForSplattingResult.MyResourceProperty2 | Should -Be 'MyNewValue2' + $result.MyResourceProperty1 | Should -Be 'MyNewValue1' + $result.MyResourceProperty2 | Should -Be 'MyNewValue2' } } } @@ -89,16 +89,16 @@ Describe 'ConvertFrom-CompareResult' -Tag 'Private' { } ) - $getDesiredStateForSplattingResult = $mockProperties | ConvertFrom-CompareResult + $result = $mockProperties | ConvertFrom-CompareResult - $getDesiredStateForSplattingResult | Should -BeOfType [System.Collections.Hashtable] + $result | Should -BeOfType [System.Collections.Hashtable] - $getDesiredStateForSplattingResult.Keys | Should -HaveCount 2 - $getDesiredStateForSplattingResult.Keys | Should -Contain 'MyResourceProperty1' - $getDesiredStateForSplattingResult.Keys | Should -Contain 'MyResourceProperty2' + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'MyResourceProperty1' + $result.Keys | Should -Contain 'MyResourceProperty2' - $getDesiredStateForSplattingResult.MyResourceProperty1 | Should -Be 'MyNewValue1' - $getDesiredStateForSplattingResult.MyResourceProperty2 | Should -Be 'MyNewValue2' + $result.MyResourceProperty1 | Should -Be 'MyNewValue1' + $result.MyResourceProperty2 | Should -Be 'MyNewValue2' } } } diff --git a/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 b/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 new file mode 100644 index 000000000..7926d5451 --- /dev/null +++ b/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 @@ -0,0 +1,127 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'ConvertTo-Reason' -Tag 'Private' { + Context 'When passing an empty collection' { + It 'Should return an empty collection' { + InModuleScope -ScriptBlock { + $mockProperties = @() + + $result = ConvertTo-Reason -Property $mockProperties -ResourceName 'MyResource' + + $result | Should -HaveCount 0 + } + } + } + + Context 'When passing a null value' { + It 'Should return an empty collection' { + InModuleScope -ScriptBlock { + $mockProperties = @() + + $result = ConvertTo-Reason -Property $null -ResourceName 'MyResource' + + $result | Should -HaveCount 0 + } + } + } + + Context 'When passing as named parameter' { + It 'Should return the correct values in a hashtable' { + InModuleScope -ScriptBlock { + $mockProperties = @( + @{ + Property = 'MyResourceProperty1' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + }, + @{ + Property = 'MyResourceProperty2' + ExpectedValue = @('MyNewValue2', 'MyNewValue3') + ActualValue = @('MyValue2', 'MyValue3') + } + ) + + $result = ConvertTo-Reason -Property $mockProperties -ResourceName 'MyResource' + + $result | Should -HaveCount 2 + + $result.Code | Should -Contain 'MyResource:MyResource:MyResourceProperty1' + $result.Phrase | Should -Contain 'The property MyResourceProperty1 should be "MyNewValue1", but was "MyValue1"' + + $result.Code | Should -Contain 'MyResource:MyResource:MyResourceProperty2' + $result.Phrase | Should -Contain 'The property MyResourceProperty2 should be ["MyNewValue2","MyNewValue3"], but was ["MyValue2","MyValue3"]' + } + } + } + + Context 'When passing in the pipeline' { + It 'Should return the correct values in a hashtable' { + InModuleScope -ScriptBlock { + $mockProperties = @( + @{ + Property = 'MyResourceProperty1' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + }, + @{ + Property = 'MyResourceProperty2' + ExpectedValue = @('MyNewValue2', 'MyNewValue3') + ActualValue = @('MyValue2', 'MyValue3') + } + ) + + $result = $mockProperties | ConvertTo-Reason -ResourceName 'MyResource' + + $result | Should -HaveCount 2 + + $result.Code | Should -Contain 'MyResource:MyResource:MyResourceProperty1' + $result.Phrase | Should -Contain 'The property MyResourceProperty1 should be "MyNewValue1", but was "MyValue1"' + + $result.Code | Should -Contain 'MyResource:MyResource:MyResourceProperty2' + $result.Phrase | Should -Contain 'The property MyResourceProperty2 should be ["MyNewValue2","MyNewValue3"], but was ["MyValue2","MyValue3"]' + } + } + } +} From 0857a35e2c86c7debbdcbcaa04ccbc6dc95e2a67 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 27 Jul 2022 15:37:23 +0200 Subject: [PATCH 93/98] Revert using newer pester code coverage --- build.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.yaml b/build.yaml index 088f94fba..d3d4d4be9 100644 --- a/build.yaml +++ b/build.yaml @@ -89,9 +89,7 @@ Pester: CoveragePercentTarget: 85 OutputPath: JaCoCo_coverage.xml OutputEncoding: ascii - # TODO: There is a bug in Pester when running unit tests for class ResourceBase when 'UseBreakpoints' is turned off. - # See error in gist: https://gist.github.com/johlju/c16dfd9587c7e066e8825fc54b33a703 - UseBreakpoints: true + UseBreakpoints: false TestResult: OutputFormat: NUnitXML OutputEncoding: ascii From 2876c4c1e75e97ca7a970f16da4187c024192026 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 27 Jul 2022 16:49:18 +0200 Subject: [PATCH 94/98] Use breakpoints for Pester coverage --- build.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index d3d4d4be9..650dd6932 100644 --- a/build.yaml +++ b/build.yaml @@ -89,7 +89,9 @@ Pester: CoveragePercentTarget: 85 OutputPath: JaCoCo_coverage.xml OutputEncoding: ascii - UseBreakpoints: false + # There is a bug in Pester when running unit tests for classes when 'UseBreakpoints' is turned off. + # See error in gist: https://gist.github.com/johlju/c16dfd9587c7e066e8825fc54b33a703 + UseBreakpoints: true TestResult: OutputFormat: NUnitXML OutputEncoding: ascii From fdc75be4412fb5420299ca3f39eb59fc35e5c1e1 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 28 Jul 2022 10:13:37 +0200 Subject: [PATCH 95/98] Fix style formatting --- source/Classes/020.SqlDatabasePermission.ps1 | 18 +- source/Private/ConvertTo-Reason.ps1 | 2 +- .../ConvertTo-SqlDscDatabasePermission.ps1 | 6 +- .../Unit/Classes/DatabasePermission.Tests.ps1 | 36 +- .../Classes/SqlDatabasePermission.Tests.ps1 | 376 +++++++++--------- .../Get-LocalizedDataRecursive.Tests.ps1 | 4 +- ...ertFrom-SqlDscDatabasePermission.Tests.ps1 | 2 +- .../Get-SqlDscDatabasePermission.Tests.ps1 | 8 +- .../Set-SqlDscDatabasePermission.Tests.ps1 | 86 ++-- .../Test-SqlDscIsDatabasePrincipal.Tests.ps1 | 14 +- 10 files changed, 276 insertions(+), 276 deletions(-) diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 46c49a827..0c1a80ef4 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -251,7 +251,7 @@ class SqlDatabasePermission : ResourceBase if (-not $this.sqlServerObject) { $connectSqlDscDatabaseEngineParameters = @{ - ServerName = $this.ServerName + ServerName = $this.ServerName InstanceName = $this.InstanceName } @@ -497,7 +497,7 @@ class SqlDatabasePermission : ResourceBase else { [DatabasePermission[]] $permissionsToRevoke += [DatabasePermission] @{ - State = $currentPermissionsForState.State + State = $currentPermissionsForState.State Permission = $permissionName } } @@ -624,7 +624,7 @@ class SqlDatabasePermission : ResourceBase { # PermissionToInclude and PermissionToExclude should be mutually exclusive from Permission $assertBoundParameterParameters = @{ - BoundParameterList = $properties + BoundParameterList = $properties MutuallyExclusiveList1 = @( 'Permission' ) @@ -638,12 +638,12 @@ class SqlDatabasePermission : ResourceBase # Get all assigned permission properties. $assignedPermissionProperty = $properties.Keys.Where({ - $_ -in @( - 'Permission', - 'PermissionToInclude', - 'PermissionToExclude' - ) - }) + $_ -in @( + 'Permission', + 'PermissionToInclude', + 'PermissionToExclude' + ) + }) # Must include either of the permission properties. if ([System.String]::IsNullOrEmpty($assignedPermissionProperty)) diff --git a/source/Private/ConvertTo-Reason.ps1 b/source/Private/ConvertTo-Reason.ps1 index dca746de4..32c80d828 100644 --- a/source/Private/ConvertTo-Reason.ps1 +++ b/source/Private/ConvertTo-Reason.ps1 @@ -65,7 +65,7 @@ function ConvertTo-Reason } $reasons += [Reason] @{ - Code = '{0}:{0}:{1}' -f $ResourceName, $currentProperty.Property + Code = '{0}:{0}:{1}' -f $ResourceName, $currentProperty.Property # Convert the object to JSON to handle complex types. Phrase = 'The property {0} should be {1}, but was {2}' -f $currentProperty.Property, ($propertyExpectedValue | ConvertTo-Json -Compress), ($propertyActualValue | ConvertTo-Json -Compress) } diff --git a/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 b/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 index d6bdccf60..68db8569a 100644 --- a/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 +++ b/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 @@ -51,8 +51,8 @@ function ConvertTo-SqlDscDatabasePermission } $databasePermissionStateExist = $permissions.Where({ - $_.State -contains $currentPermissionState - }) | + $_.State -contains $currentPermissionState + }) | Select-Object -First 1 if ($databasePermissionStateExist) @@ -62,7 +62,7 @@ function ConvertTo-SqlDscDatabasePermission else { $databasePermission = [DatabasePermission] @{ - State = $currentPermissionState + State = $currentPermissionState Permission = [System.String[]] @() } } diff --git a/tests/Unit/Classes/DatabasePermission.Tests.ps1 b/tests/Unit/Classes/DatabasePermission.Tests.ps1 index b0cdbef28..2cbeeaf2e 100644 --- a/tests/Unit/Classes/DatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/DatabasePermission.Tests.ps1 @@ -171,7 +171,7 @@ Describe 'DatabasePermission' -Tag 'DatabasePermission' { It 'Should return a value less than zero' { $mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { [DatabasePermission] @{ - State = 'Grant' + State = 'Grant' Permission = 'Select' } } @@ -199,28 +199,28 @@ Describe 'DatabasePermission' -Tag 'DatabasePermission' { Context 'When the instance has the state '''' and object has state ''''' -ForEach @( @{ MockInstanceState = 'Grant' - MockObjectState = 'GrantWithGrant' + MockObjectState = 'GrantWithGrant' } @{ MockInstanceState = 'Grant' - MockObjectState = 'Deny' + MockObjectState = 'Deny' } @{ MockInstanceState = 'GrantWithGrant' - MockObjectState = 'Deny' + MockObjectState = 'Deny' } ) { It 'Should return a value less than zero' { $mockDatabasePermissionInstance1 = InModuleScope -Parameters $_ -ScriptBlock { [DatabasePermission] @{ - State = $MockInstanceState + State = $MockInstanceState Permission = 'Select' } } $mockDatabasePermissionInstance2 = InModuleScope -Parameters $_ -ScriptBlock { [DatabasePermission] @{ - State = $MockObjectState + State = $MockObjectState Permission = 'Select' } } @@ -234,28 +234,28 @@ Describe 'DatabasePermission' -Tag 'DatabasePermission' { Context 'When the instance has the state '''' and object has state ''''' -ForEach @( @{ MockInstanceState = 'Deny' - MockObjectState = 'Grant' + MockObjectState = 'Grant' } @{ MockInstanceState = 'GrantWithGrant' - MockObjectState = 'Grant' + MockObjectState = 'Grant' } @{ MockInstanceState = 'Deny' - MockObjectState = 'GrantWithGrant' + MockObjectState = 'GrantWithGrant' } ) { It 'Should return a value less than zero' { $mockDatabasePermissionInstance1 = InModuleScope -Parameters $_ -ScriptBlock { [DatabasePermission] @{ - State = $MockInstanceState + State = $MockInstanceState Permission = 'Select' } } $mockDatabasePermissionInstance2 = InModuleScope -Parameters $_ -ScriptBlock { [DatabasePermission] @{ - State = $MockObjectState + State = $MockObjectState Permission = 'Select' } } @@ -268,7 +268,7 @@ Describe 'DatabasePermission' -Tag 'DatabasePermission' { It 'Should return a value less than zero' { $mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { [DatabasePermission] @{ - State = 'Grant' + State = 'Grant' Permission = 'Select' } } @@ -282,28 +282,28 @@ Describe 'DatabasePermission' -Tag 'DatabasePermission' { Context 'When the instance has the state '''' and object has state ''''' -ForEach @( @{ MockInstanceState = 'Grant' - MockObjectState = 'Grant' + MockObjectState = 'Grant' } @{ MockInstanceState = 'GrantWithGrant' - MockObjectState = 'GrantWithGrant' + MockObjectState = 'GrantWithGrant' } @{ MockInstanceState = 'Deny' - MockObjectState = 'Deny' + MockObjectState = 'Deny' } ) { It 'Should return a value less than zero' { $mockDatabasePermissionInstance1 = InModuleScope -Parameters $_ -ScriptBlock { [DatabasePermission] @{ - State = $MockInstanceState + State = $MockInstanceState Permission = 'Select' } } $mockDatabasePermissionInstance2 = InModuleScope -Parameters $_ -ScriptBlock { [DatabasePermission] @{ - State = $MockObjectState + State = $MockObjectState Permission = 'Select' } } @@ -339,7 +339,7 @@ Describe 'DatabasePermission' -Tag 'DatabasePermission' { foreach ($currentMockState in $MockState) { [DatabasePermission] @{ - State = $currentMockState + State = $currentMockState Permission = 'Select' } } diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 index 673f39107..3fd2edb24 100644 --- a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -339,10 +339,10 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { It 'Should return empty collections for each state' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - }) + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) $currentState.Credential | Should -BeNullOrEmpty @@ -378,10 +378,10 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { ) $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - }) + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) $currentState.Credential | Should -BeOfType [System.Management.Automation.PSCredential] @@ -454,10 +454,10 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { It 'Should return correct values for state Grant and empty collections for the two other states' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - }) + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) $currentState.Credential | Should -BeNullOrEmpty @@ -530,10 +530,10 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - }) + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) $currentState.Credential | Should -BeNullOrEmpty @@ -571,7 +571,7 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { DatabaseName = 'MockDatabaseName' InstanceName = 'NamedInstance' PermissionToInclude = [DatabasePermission] @{ - State = 'Grant' + State = 'Grant' Permission = 'update' } } @@ -610,10 +610,10 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - }) + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) $currentState.Credential | Should -BeNullOrEmpty @@ -654,7 +654,7 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { DatabaseName = 'MockDatabaseName' InstanceName = 'NamedInstance' PermissionToInclude = [DatabasePermission] @{ - State = 'Grant' + State = 'Grant' Permission = 'alter' } } @@ -693,10 +693,10 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - }) + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) $currentState.Credential | Should -BeNullOrEmpty @@ -739,7 +739,7 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { DatabaseName = 'MockDatabaseName' InstanceName = 'NamedInstance' PermissionToExclude = [DatabasePermission] @{ - State = 'Grant' + State = 'Grant' Permission = 'alter' } } @@ -778,10 +778,10 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - }) + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) $currentState.Credential | Should -BeNullOrEmpty @@ -822,7 +822,7 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { DatabaseName = 'MockDatabaseName' InstanceName = 'NamedInstance' PermissionToExclude = [DatabasePermission] @{ - State = 'Grant' + State = 'Grant' Permission = 'update' } } @@ -861,10 +861,10 @@ Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { InModuleScope -ScriptBlock { $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - }) + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) $currentState.Credential | Should -BeNullOrEmpty @@ -961,14 +961,14 @@ Describe 'SqlDatabasePermission\Set()' -Tag 'Set' { # Mock method Compare() which is called by the base method Set() Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { return @{ - Property = 'Permission' + Property = 'Permission' ExpectedValue = [DatabasePermission[]] @( [DatabasePermission] @{ State = 'Grant' Permission = @('Connect', 'Update') } ) - ActualValue = [DatabasePermission[]] @( + ActualValue = [DatabasePermission[]] @( [DatabasePermission] @{ State = 'Grant' Permission = @('Connect') @@ -1039,14 +1039,14 @@ Describe 'SqlDatabasePermission\Test()' -Tag 'Test' { # Mock method Compare() which is called by the base method Set() Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { return @{ - Property = 'Permission' + Property = 'Permission' ExpectedValue = [DatabasePermission[]] @( [DatabasePermission] @{ State = 'Grant' Permission = @('Connect', 'Update') } ) - ActualValue = [DatabasePermission[]] @( + ActualValue = [DatabasePermission[]] @( [DatabasePermission] @{ State = 'Grant' Permission = @('Connect') @@ -1108,8 +1108,8 @@ Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { { # This test does not pass any properties to set as it is not necessary for this test. $mockSqlDatabasePermissionInstance.Modify(@{ - Permission = [DatabasePermission[]] @() - }) + Permission = [DatabasePermission[]] @() + }) } | Should -Throw -ExpectedMessage $mockErrorRecord } } @@ -1176,21 +1176,21 @@ Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { InModuleScope -ScriptBlock { { $mockSqlDatabasePermissionInstance.Modify(@{ - Permission = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('Connect') - } - [DatabasePermission] @{ - State = 'GrantWithGrant' - Permission = @('Update') - } - [DatabasePermission] @{ - State = 'Deny' - Permission = @() - } - ) - }) + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) } | Should -Not -Throw } @@ -1266,21 +1266,21 @@ Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { InModuleScope -ScriptBlock { { $mockSqlDatabasePermissionInstance.Modify(@{ - Permission = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('Connect') - } - [DatabasePermission] @{ - State = 'GrantWithGrant' - Permission = @() - } - [DatabasePermission] @{ - State = 'Deny' - Permission = @() - } - ) - }) + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) } | Should -Not -Throw } @@ -1312,10 +1312,10 @@ Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { BeforeAll { InModuleScope -ScriptBlock { $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - PermissionToInclude = [DatabasePermission[]] @( + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToInclude = [DatabasePermission[]] @( [DatabasePermission] @{ State = 'Grant' Permission = @('Connect') @@ -1368,21 +1368,21 @@ Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { InModuleScope -ScriptBlock { { $mockSqlDatabasePermissionInstance.Modify(@{ - PermissionToInclude = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('Connect') - } - [DatabasePermission] @{ - State = 'GrantWithGrant' - Permission = @('Update') - } - [DatabasePermission] @{ - State = 'Deny' - Permission = @() - } - ) - }) + PermissionToInclude = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) } | Should -Not -Throw } @@ -1404,10 +1404,10 @@ Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { BeforeAll { InModuleScope -ScriptBlock { $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ - Name = 'MockUserName' - DatabaseName = 'MockDatabaseName' - InstanceName = 'NamedInstance' - PermissionToExclude = [DatabasePermission[]] @( + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToExclude = [DatabasePermission[]] @( [DatabasePermission] @{ State = 'Grant' Permission = @('Connect') @@ -1460,21 +1460,21 @@ Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { InModuleScope -ScriptBlock { { $mockSqlDatabasePermissionInstance.Modify(@{ - PermissionToExclude = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('Connect') - } - [DatabasePermission] @{ - State = 'GrantWithGrant' - Permission = @('Update') - } - [DatabasePermission] @{ - State = 'Deny' - Permission = @() - } - ) - }) + PermissionToExclude = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) } | Should -Not -Throw } @@ -1565,21 +1565,21 @@ Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { InModuleScope -ScriptBlock { { $mockSqlDatabasePermissionInstance.Modify(@{ - Permission = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('Connect') - } - [DatabasePermission] @{ - State = 'GrantWithGrant' - Permission = @() - } - [DatabasePermission] @{ - State = 'Deny' - Permission = @() - } - ) - }) + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) } | Should -Throw -ExpectedMessage $mockErrorRecord } } @@ -1658,21 +1658,21 @@ Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { InModuleScope -ScriptBlock { { $mockSqlDatabasePermissionInstance.Modify(@{ - Permission = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = @('Connect') - } - [DatabasePermission] @{ - State = 'GrantWithGrant' - Permission = @() - } - [DatabasePermission] @{ - State = 'Deny' - Permission = @() - } - ) - }) + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) } | Should -Throw -ExpectedMessage $mockErrorRecord } } @@ -1698,9 +1698,9 @@ Describe 'SqlDatabasePermission\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { { $mockSqlDatabasePermissionInstance.AssertProperties(@{ - Permission = [DatabasePermission[]] @([DatabasePermission] @{}) - PermissionToInclude = [DatabasePermission[]] @([DatabasePermission] @{}) - }) + Permission = [DatabasePermission[]] @([DatabasePermission] @{}) + PermissionToInclude = [DatabasePermission[]] @([DatabasePermission] @{}) + }) } | Should -Throw -ExpectedMessage '*DRC0010*' } } @@ -1711,9 +1711,9 @@ Describe 'SqlDatabasePermission\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { { $mockSqlDatabasePermissionInstance.AssertProperties(@{ - Permission = [DatabasePermission[]] @([DatabasePermission] @{}) - PermissionToExclude = [DatabasePermission[]] @([DatabasePermission] @{}) - }) + Permission = [DatabasePermission[]] @([DatabasePermission] @{}) + PermissionToExclude = [DatabasePermission[]] @([DatabasePermission] @{}) + }) } | Should -Throw -ExpectedMessage '*DRC0010*' } } @@ -1753,15 +1753,15 @@ Describe 'SqlDatabasePermission\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -Parameters $_ -ScriptBlock { { $mockSqlDatabasePermissionInstance.AssertProperties(@{ - $MockPropertyName = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - } - [DatabasePermission] @{ - State = 'Grant' - } - ) - }) + $MockPropertyName = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + } + [DatabasePermission] @{ + State = 'Grant' + } + ) + }) } | Should -Throw -ExpectedMessage $mockErrorMessage } } @@ -1776,16 +1776,16 @@ Describe 'SqlDatabasePermission\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -Parameters $_ -ScriptBlock { { $mockSqlDatabasePermissionInstance.AssertProperties(@{ - Permission = [DatabasePermission[]] @( - # Missing state Deny. - [DatabasePermission] @{ - State = 'Grant' - } - [DatabasePermission] @{ - State = 'GrantWithGrant' - } - ) - }) + Permission = [DatabasePermission[]] @( + # Missing state Deny. + [DatabasePermission] @{ + State = 'Grant' + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + } + ) + }) } | Should -Throw -ExpectedMessage $mockErrorMessage } } @@ -1810,17 +1810,17 @@ Describe 'SqlDatabasePermission\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -Parameters $_ -ScriptBlock { { $mockSqlDatabasePermissionInstance.AssertProperties(@{ - $MockPropertyName = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - Permission = 'Select' - } - [DatabasePermission] @{ - State = 'Deny' - Permission = 'Select' - } - ) - }) + $MockPropertyName = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = 'Select' + } + [DatabasePermission] @{ + State = 'Deny' + Permission = 'Select' + } + ) + }) } | Should -Throw -ExpectedMessage $mockErrorMessage } } @@ -1842,22 +1842,22 @@ Describe 'SqlDatabasePermission\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -Parameters $_ -ScriptBlock { { $mockSqlDatabasePermissionInstance.AssertProperties(@{ - $MockPropertyName = [DatabasePermission[]] @( - [DatabasePermission] @{ - State = 'Grant' - <# + $MockPropertyName = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + <# This should not be able to be $null since the property is mandatory but do allow empty collection. So no need to test using $null value. #> - Permission = @() - } - [DatabasePermission] @{ - State = 'Deny' - Permission = 'Select' - } - ) - }) + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = 'Select' + } + ) + }) } | Should -Throw -ExpectedMessage $mockErrorMessage } } diff --git a/tests/Unit/Private/Get-LocalizedDataRecursive.Tests.ps1 b/tests/Unit/Private/Get-LocalizedDataRecursive.Tests.ps1 index 743118a16..b9da388a1 100644 --- a/tests/Unit/Private/Get-LocalizedDataRecursive.Tests.ps1 +++ b/tests/Unit/Private/Get-LocalizedDataRecursive.Tests.ps1 @@ -97,7 +97,7 @@ Describe 'Get-LocalizedDataRecursive' -Tag 'Private' { Context 'When passing value with named parameter' { It 'Should return the correct localization strings' { InModuleScope -ScriptBlock { - $result = Get-LocalizedDataRecursive -ClassName @('MyClassResource','MyBaseClass') + $result = Get-LocalizedDataRecursive -ClassName @('MyClassResource', 'MyBaseClass') $result.Keys | Should -HaveCount 2 $result.Keys | Should -Contain 'ClassStringKey' @@ -111,7 +111,7 @@ Describe 'Get-LocalizedDataRecursive' -Tag 'Private' { Context 'When passing value in pipeline' { It 'Should return the correct localization strings' { InModuleScope -ScriptBlock { - $result = @('MyClassResource','MyBaseClass') | Get-LocalizedDataRecursive + $result = @('MyClassResource', 'MyBaseClass') | Get-LocalizedDataRecursive $result.Keys | Should -HaveCount 2 $result.Keys | Should -Contain 'ClassStringKey' diff --git a/tests/Unit/Public/ConvertFrom-SqlDscDatabasePermission.Tests.ps1 b/tests/Unit/Public/ConvertFrom-SqlDscDatabasePermission.Tests.ps1 index 32022359f..ea83d9b01 100644 --- a/tests/Unit/Public/ConvertFrom-SqlDscDatabasePermission.Tests.ps1 +++ b/tests/Unit/Public/ConvertFrom-SqlDscDatabasePermission.Tests.ps1 @@ -49,7 +49,7 @@ Describe 'ConvertFrom-SqlDscDatabasePermission' -Tag 'Public' { BeforeAll { $mockPermission = InModuleScope -ScriptBlock { [DatabasePermission] @{ - State = 'Grant' + State = 'Grant' Permission = @( 'Connect' 'Alter' diff --git a/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 b/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 index 65c34063a..bbc9375c6 100644 --- a/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 +++ b/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 @@ -82,8 +82,8 @@ Describe 'Get-SqlDscDatabasePermission' -Tag 'Public' { return @{ 'AdventureWorks' = New-Object -TypeName Object | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'AdventureWorks' -PassThru -Force - } - } -PassThru -Force + } + } -PassThru -Force } Context 'When specifying to throw on error' { @@ -114,8 +114,8 @@ Describe 'Get-SqlDscDatabasePermission' -Tag 'Public' { return @{ 'AdventureWorks' = New-Object -TypeName Object | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'AdventureWorks' -PassThru -Force - } - } -PassThru -Force + } + } -PassThru -Force Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { return $false diff --git a/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 b/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 index 1cb04e2d9..7d77f4d98 100644 --- a/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 +++ b/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 @@ -55,9 +55,9 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { $script:mockDefaultParameters = @{ DatabaseName = 'MissingDatabase' - Name = 'KnownUser' - State = 'Grant' - Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]::new() + Name = 'KnownUser' + State = 'Grant' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]::new() } } @@ -83,9 +83,9 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { $script:mockDefaultParameters = @{ DatabaseName = 'MissingDatabase' - Name = 'KnownUser' - State = 'Grant' - Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]::new() + Name = 'KnownUser' + State = 'Grant' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]::new() } } @@ -116,9 +116,9 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { $script:mockDefaultParameters = @{ DatabaseName = 'AdventureWorks' - Name = 'UnknownUser' - State = 'Grant' - Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]::new() + Name = 'UnknownUser' + State = 'Grant' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]::new() } } @@ -151,11 +151,11 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ - Confirm = $false + Confirm = $false DatabaseName = 'AdventureWorks' - Name = 'Zebes\SamusAran' - State = 'Deny' - Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Name = 'Zebes\SamusAran' + State = 'Deny' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ Connect = $true } } @@ -191,11 +191,11 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ - Force = $true + Force = $true DatabaseName = 'AdventureWorks' - Name = 'Zebes\SamusAran' - State = 'Deny' - Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Name = 'Zebes\SamusAran' + State = 'Deny' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ Connect = $true } } @@ -231,11 +231,11 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ - WhatIf = $true + WhatIf = $true DatabaseName = 'AdventureWorks' - Name = 'Zebes\SamusAran' - State = 'Deny' - Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Name = 'Zebes\SamusAran' + State = 'Deny' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ Connect = $true } } @@ -271,11 +271,11 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ - Confirm = $false + Confirm = $false DatabaseName = 'AdventureWorks' - Name = 'Zebes\SamusAran' - State = 'Grant' - Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Name = 'Zebes\SamusAran' + State = 'Grant' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ Connect = $true } } @@ -335,12 +335,12 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ - Confirm = $false + Confirm = $false DatabaseName = 'AdventureWorks' - Name = 'Zebes\SamusAran' - State = 'Grant' - WithGrant = $true - Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Name = 'Zebes\SamusAran' + State = 'Grant' + WithGrant = $true + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ Connect = $true } } @@ -385,11 +385,11 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ - Confirm = $false + Confirm = $false DatabaseName = 'AdventureWorks' - Name = 'Zebes\SamusAran' - State = 'Revoke' - Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Name = 'Zebes\SamusAran' + State = 'Revoke' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ Connect = $true } } @@ -452,12 +452,12 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ - Confirm = $false + Confirm = $false DatabaseName = 'AdventureWorks' - Name = 'Zebes\SamusAran' - State = 'Revoke' - WithGrant = $true - Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Name = 'Zebes\SamusAran' + State = 'Revoke' + WithGrant = $true + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ Connect = $true } } @@ -502,11 +502,11 @@ Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { } $script:mockDefaultParameters = @{ - Confirm = $false + Confirm = $false DatabaseName = 'AdventureWorks' - Name = 'Zebes\SamusAran' - State = 'Deny' - Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Name = 'Zebes\SamusAran' + State = 'Deny' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ Connect = $true } } diff --git a/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 b/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 index 6a3c0ee14..1ef851439 100644 --- a/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 +++ b/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 @@ -89,7 +89,7 @@ Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { } -PassThru | Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { return @{ - 'db_datareader' = New-Object -TypeName Object | + 'db_datareader' = New-Object -TypeName Object | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force @@ -131,7 +131,7 @@ Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { } -PassThru | Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { return @{ - 'db_datareader' = New-Object -TypeName Object | + 'db_datareader' = New-Object -TypeName Object | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force @@ -188,7 +188,7 @@ Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { } -PassThru | Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { return @{ - 'db_datareader' = New-Object -TypeName Object | + 'db_datareader' = New-Object -TypeName Object | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force @@ -237,7 +237,7 @@ Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { } -PassThru | Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { return @{ - 'db_datareader' = New-Object -TypeName Object | + 'db_datareader' = New-Object -TypeName Object | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force @@ -286,7 +286,7 @@ Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { return ( @{ - 'db_datareader' = New-Object -TypeName Object | + 'db_datareader' = New-Object -TypeName Object | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force @@ -329,7 +329,7 @@ Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { } -PassThru | Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { return @{ - 'db_datareader' = New-Object -TypeName Object | + 'db_datareader' = New-Object -TypeName Object | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force @@ -378,7 +378,7 @@ Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { return ( @{ - 'db_datareader' = New-Object -TypeName Object | + 'db_datareader' = New-Object -TypeName Object | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force From 7c5058bddc8fabb1e998dab11b579f672dbecea4 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 28 Jul 2022 15:08:40 +0200 Subject: [PATCH 96/98] Update CHANGELOG.md --- CHANGELOG.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b084d153a..133de4fe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 the existing DSC Community rules and style guideline were added. - Added the Visual Studio Code extension _Code Spell Checker_ to the list of recommended Visual Studio Code extensions. + - Added a file `prefix.ps1` which content is placed first in the built module + (.psm1). This file imports dependent modules, and imports localized strings + used by private and public commands. + - The following classes were added to the module: + - `DatabasePermission` - complex type for the DSC resource SqlDatabasePermission. + - `Ensure` - Enum to be used for the property `Ensure` in class-based + resources. + - `Reason` - Used by method `Get()` to return the reason a property is not + in desired state. + - `ResourceBase` - class that can be inherited by class-based resource and + provides functionality meant simplify the creating of class-based resource. + - The following private functions were added to the module (see comment-based + help for more information): + - `ConvertFrom-CompareResult` + - `ConvertTo-Reason` + - `Get-ClassName` + - `Get-DscProperty` + - `Get-LocalizedDataRecursive` + - `Test-ResourceHasProperty` + - `Test-ResourcePropertyIsAssigned` + - The following public functions were added to the module (see comment-based + help for more information): + - `Connect-SqlDscDatabaseEngine` + - `ConvertFrom-SqlDscDatabasePermission` + - `ConvertTo-SqlDscDatabasePermission` + - `Get-SqlDscDatabasePermission` + - `Set-SqlDscDatabasePermission` + - `Test-SqlDscIsDatabasePrincipal` - CommonTestHelper - `Import-SqlModuleStub` - Added the optional parameter **PasThru** that, if used, will return the @@ -59,8 +87,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bumped Stale task to v5 in the GitHub workflow. - Make it possible to publish code coverage on failed test runs, and when re-run a fail job. + - Exclude Script Analyzer rule **TypeNotFound** in the file `.vscode/analyzersettings.psd1`. + - Update CONTRIBUTING.md describing error handling in commands and class-based + resources. + - The QA tests are now run in Windows PowerShell due to a bug in PowerShell 7 + that makes class-based resource using inheritance to not work. + - The QA test are excluding the rule **TypeNotFound** because it cannot + run on the source files (there is a new issue that is tracking so this + rule is only run on the built module). + - The Pester code coverage has been switched to use the older functionality + that uses breakpoints to calculate coverage. Newer functionality sometimes + throw an exception when used in conjunction with class-based resources.ยจ + - The SMO stubs (used in the unit tests) was updated to remove a bug related + to the type `DatabasePermissionInfo` when used with the type `DatabasePermissionSet`. + The stubs suggested that the property `PermissionType` (of type `DatabasePermissionSet`) + in `DatabasePermissionInfo` should have been a array `DatabasePermissionSet[]`. + This conflicted with real SMO as it does not pass an array, but instead + a single `DatabasePermissionSet`. The stubs was modified to mimic the + real SMO. At the same time some old mock code in the SMO stubs was removed + as it was no longer in use. - Wiki - add introduction and links to DSC technology +- SqlServerDsc.Common + - The parameter `SetupCredential` of the function `Connect-SQL` was renamed + to `Credential` and the parameter name `SetupCredential` was made a + parameter alias. - SqlLogin - BREAKING CHANGE: The parameters `LoginMustChangePassword`, `LoginPasswordExpirationEnabled`, and `LoginPasswordPolicyEnforced` no longer have a default value of `$true`. From fcb2ddc4ceaa0555d13043fa2b73af634c17e491 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 29 Jul 2022 10:23:27 +0200 Subject: [PATCH 97/98] Fix review comments --- CHANGELOG.md | 4 +- source/Classes/010.ResourceBase.ps1 | 6 +-- source/Classes/020.SqlDatabasePermission.ps1 | 4 +- source/Private/ConvertFrom-CompareResult.ps1 | 3 ++ source/Private/ConvertTo-Reason.ps1 | 9 +++- source/Private/Get-ClassName.ps1 | 18 ++++++-- source/Private/Get-DscProperty.ps1 | 43 +++++++++++++++++-- source/Private/Get-LocalizedDataRecursive.ps1 | 14 +++++- ...=> Test-ResourceDscPropertyIsAssigned.ps1} | 13 ++++-- ...ty.ps1 => Test-ResourceHasDscProperty.ps1} | 19 ++++++-- .../Public/Connect-SqlDscDatabaseEngine.ps1 | 3 ++ .../ConvertFrom-SqlDscDatabasePermission.ps1 | 9 ++-- .../ConvertTo-SqlDscDatabasePermission.ps1 | 9 ++-- .../Public/Get-SqlDscDatabasePermission.ps1 | 15 +++---- .../Public/Set-SqlDscDatabasePermission.ps1 | 14 +++--- .../Public/Test-SqlDscIsDatabasePrincipal.ps1 | 33 +++++++++++--- tests/Unit/Private/Get-ClassName.Tests.ps1 | 8 ++-- ...t-ResourceDscPropertyIsAssigned.Tests.ps1} | 6 +-- ... => Test-ResourceHasDscProperty.Tests.ps1} | 12 +++--- .../Get-SqlDscDatabasePermission.Tests.ps1 | 40 +++++++++-------- tests/Unit/Stubs/SMO.cs | 21 +++------ 21 files changed, 204 insertions(+), 99 deletions(-) rename source/Private/{Test-ResourcePropertyIsAssigned.ps1 => Test-ResourceDscPropertyIsAssigned.ps1} (57%) rename source/Private/{Test-ResourceHasProperty.ps1 => Test-ResourceHasDscProperty.ps1} (62%) rename tests/Unit/Private/{Test-ResourcePropertyIsAssigned.Tests.ps1 => Test-ResourceDscPropertyIsAssigned.Tests.ps1} (92%) rename tests/Unit/Private/{Test-ResourceHasProperty.Tests.ps1 => Test-ResourceHasDscProperty.Tests.ps1} (91%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01d48586c..34d312b52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,8 +56,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Get-ClassName` - `Get-DscProperty` - `Get-LocalizedDataRecursive` - - `Test-ResourceHasProperty` - - `Test-ResourcePropertyIsAssigned` + - `Test-ResourceHasDscProperty` + - `Test-ResourceDscPropertyIsAssigned` - The following public functions were added to the module (see comment-based help for more information): - `Connect-SqlDscDatabaseEngine` diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index 5db649f0a..b0a730ccf 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -76,7 +76,7 @@ class ResourceBase state for property Ensure cannot be determined until the method Compare() has run to determined if other properties are not in desired state. #> - if (($this | Test-ResourceHasProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) + if (($this | Test-ResourceHasDscProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) { $ignoreProperty += 'Ensure' } @@ -91,7 +91,7 @@ class ResourceBase Return the correct values for Ensure property if the derived DSC resource has such property and it hasn't been already set by GetCurrentState(). #> - if (($this | Test-ResourceHasProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) + if (($this | Test-ResourceHasDscProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) { if ($propertiesNotInDesiredState) { @@ -136,7 +136,7 @@ class ResourceBase Return the correct values for Reasons property if the derived DSC resource has such property and it hasn't been already set by GetCurrentState(). #> - if (($this | Test-ResourceHasProperty -Name 'Reasons') -and -not $getCurrentStateResult.ContainsKey('Reasons')) + if (($this | Test-ResourceHasDscProperty -Name 'Reasons') -and -not $getCurrentStateResult.ContainsKey('Reasons')) { # Always return an empty array if all properties are in desired state. $dscResourceObject.Reasons = $propertiesNotInDesiredState | diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index 0c1a80ef4..02bfa5092 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -324,7 +324,7 @@ class SqlDatabasePermission : ResourceBase } } - $isPropertyPermissionToIncludeAssigned = $this | Test-ResourcePropertyIsAssigned -Name 'PermissionToInclude' + $isPropertyPermissionToIncludeAssigned = $this | Test-ResourceDscPropertyIsAssigned -Name 'PermissionToInclude' if ($isPropertyPermissionToIncludeAssigned) { @@ -374,7 +374,7 @@ class SqlDatabasePermission : ResourceBase } } - $isPropertyPermissionToExcludeAssigned = $this | Test-ResourcePropertyIsAssigned -Name 'PermissionToExclude' + $isPropertyPermissionToExcludeAssigned = $this | Test-ResourceDscPropertyIsAssigned -Name 'PermissionToExclude' if ($isPropertyPermissionToExcludeAssigned) { diff --git a/source/Private/ConvertFrom-CompareResult.ps1 b/source/Private/ConvertFrom-CompareResult.ps1 index eaf799c5b..a917e70b9 100644 --- a/source/Private/ConvertFrom-CompareResult.ps1 +++ b/source/Private/ConvertFrom-CompareResult.ps1 @@ -8,6 +8,9 @@ .EXAMPLE ConvertFrom-CompareResult -CompareResult (Compare-DscParameterState) + Returns a hashtable that contain all the properties not in desired state + and their expected value. + .OUTPUTS [System.Collections.Hashtable] #> diff --git a/source/Private/ConvertTo-Reason.ps1 b/source/Private/ConvertTo-Reason.ps1 index 32c80d828..fe5c2e0cb 100644 --- a/source/Private/ConvertTo-Reason.ps1 +++ b/source/Private/ConvertTo-Reason.ps1 @@ -10,8 +10,15 @@ .PARAMETER Property The result from the command Compare-DscParameterState. + .PARAMETER ResourceName + The name of the resource. Will be used to populate the property Code with + the correct value. + .EXAMPLE - ConvertTo-Reason -Property (Compare-DscParameterState) + ConvertTo-Reason -Property (Compare-DscParameterState) -ResourceName 'MyResource' + + Returns an array of `[Reason]` that contain all the properties not in desired + state and why a specific property is not in desired state. .OUTPUTS [Reason[]] diff --git a/source/Private/Get-ClassName.ps1 b/source/Private/Get-ClassName.ps1 index 648db7fdc..06745e026 100644 --- a/source/Private/Get-ClassName.ps1 +++ b/source/Private/Get-ClassName.ps1 @@ -6,13 +6,25 @@ .PARAMETER InputObject The object to be evaluated. + .PARAMETER Recurse + Specifies if the class name of inherited classes shall be returned. The + recursive stops when the first object of the type `[System.Object]` is + found. + + .EXAMPLE + Get-ClassName -InputObject $this -Recurse + + Get the class name of the current instance and all the inherited (parent) + classes. + .OUTPUTS - Returns a string array with at least one item. + [System.String[]] #> function Get-ClassName { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because the rule does not understands that the command returns [System.String[]] when using , (comma) in the return statement')] [CmdletBinding()] - [OutputType([System.Object[]])] + [OutputType([System.String[]])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] @@ -39,5 +51,5 @@ function Get-ClassName } } - return , $class + return , [System.String[]] $class } diff --git a/source/Private/Get-DscProperty.ps1 b/source/Private/Get-DscProperty.ps1 index 305df0f1d..b77ffa9dd 100644 --- a/source/Private/Get-DscProperty.ps1 +++ b/source/Private/Get-DscProperty.ps1 @@ -1,16 +1,51 @@ <# .SYNOPSIS - Returns all of the DSC resource key properties and their values. + Returns DSC resource properties that is part of a class-based DSC resource. .DESCRIPTION - Returns all of the DSC resource key properties and their values. + Returns DSC resource properties that is part of a class-based DSC resource. + The properties can be filtered using name, type, or has been assigned a value. .PARAMETER InputObject The object that contain one or more key properties. + .PARAMETER Name + Specifies one or more property names to return. If left out all properties + are returned. + + .PARAMETER Type + Specifies one or more property types to return. If left out all property + types are returned. + + .PARAMETER HasValue + Specifies to return only properties that has been assigned a non-null value. + If left out all properties are returned regardless if there is a value + assigned or not. + + .EXAMPLE + Get-DscProperty -InputObject $this + + Returns all DSC resource properties of the DSC resource. + + .EXAMPLE + Get-DscProperty -InputObject $this -Name @('MyProperty1', 'MyProperty2') + + Returns the specified DSC resource properties names of the DSC resource. + + .EXAMPLE + Get-DscProperty -InputObject $this -Type @('Mandatory', 'Optional') + + Returns the specified DSC resource property types of the DSC resource. + + .EXAMPLE + Get-DscProperty -InputObject $this -Type @('Optional') -HasValue + + Returns the specified DSC resource property types of the DSC resource, + but only those properties that has been assigned a non-null value. + .OUTPUTS - Hashtable + [System.Collections.Hashtable] #> function Get-DscProperty { @@ -93,7 +128,7 @@ function Get-DscProperty { if ($HasValue.IsPresent) { - $isAssigned = Test-ResourcePropertyIsAssigned -Name $currentProperty -InputObject $InputObject + $isAssigned = Test-ResourceDscPropertyIsAssigned -Name $currentProperty -InputObject $InputObject if (-not $isAssigned) { diff --git a/source/Private/Get-LocalizedDataRecursive.ps1 b/source/Private/Get-LocalizedDataRecursive.ps1 index 6f05a055a..b32e18aea 100644 --- a/source/Private/Get-LocalizedDataRecursive.ps1 +++ b/source/Private/Get-LocalizedDataRecursive.ps1 @@ -16,8 +16,20 @@ .PARAMETER ClassName An array of class names, normally provided by `Get-ClassName -Recurse`. + .EXAMPLE + Get-LocalizedDataRecursive -ClassName $InputObject.GetType().FullName + + Returns a hashtable containing all the localized strings for the current + instance. + + .EXAMPLE + Get-LocalizedDataRecursive -ClassName (Get-ClassNamn -InputObject $this -Recurse) + + Returns a hashtable containing all the localized strings for the current + instance and any inherited (parent) classes. + .OUTPUTS - Returns a string array with at least one item. + [System.Collections.Hashtable] #> function Get-LocalizedDataRecursive { diff --git a/source/Private/Test-ResourcePropertyIsAssigned.ps1 b/source/Private/Test-ResourceDscPropertyIsAssigned.ps1 similarity index 57% rename from source/Private/Test-ResourcePropertyIsAssigned.ps1 rename to source/Private/Test-ResourceDscPropertyIsAssigned.ps1 index 9f59262ff..5be5685df 100644 --- a/source/Private/Test-ResourcePropertyIsAssigned.ps1 +++ b/source/Private/Test-ResourceDscPropertyIsAssigned.ps1 @@ -1,9 +1,9 @@ <# .SYNOPSIS - Tests wether the class-based resource property is assigned a non-null value. + Tests whether the class-based resource property is assigned a non-null value. .DESCRIPTION - Tests wether the class-based resource property is assigned a non-null value. + Tests whether the class-based resource property is assigned a non-null value. .PARAMETER InputObject Specifies the object that contain the property. @@ -11,10 +11,15 @@ .PARAMETER Name Specifies the name of the property. + .EXAMPLE + Test-ResourceDscPropertyIsAssigned -InputObject $this -Name 'MyDscProperty' + + Returns $true or $false whether the property is assigned or not. + .OUTPUTS - [Boolean] + [System.Boolean] #> -function Test-ResourcePropertyIsAssigned +function Test-ResourceDscPropertyIsAssigned { [CmdletBinding()] [OutputType([System.Boolean])] diff --git a/source/Private/Test-ResourceHasProperty.ps1 b/source/Private/Test-ResourceHasDscProperty.ps1 similarity index 62% rename from source/Private/Test-ResourceHasProperty.ps1 rename to source/Private/Test-ResourceHasDscProperty.ps1 index 0c7ae5a74..775582cfb 100644 --- a/source/Private/Test-ResourceHasProperty.ps1 +++ b/source/Private/Test-ResourceHasDscProperty.ps1 @@ -1,9 +1,9 @@ <# .SYNOPSIS - Tests wether the class-based resource has the specified property. + Tests whether the class-based resource has the specified property. .DESCRIPTION - Tests wether the class-based resource has the specified property. + Tests whether the class-based resource has the specified property. .PARAMETER InputObject Specifies the object that should be tested for existens of the specified @@ -16,10 +16,21 @@ Specifies if the property should be evaluated to have a non-value. If the property exist but is assigned `$null` the command returns `$false`. + .EXAMPLE + Test-ResourceHasDscProperty -InputObject $this -Name 'MyDscProperty' + + Returns $true or $false whether the property exist or not. + + .EXAMPLE + Test-ResourceHasDscProperty -InputObject $this -Name 'MyDscProperty' -HasValue + + Returns $true if the property exist and is assigned a non-null value, if not + $false is returned. + .OUTPUTS - [Boolean] + [System.Boolean] #> -function Test-ResourceHasProperty +function Test-ResourceHasDscProperty { [CmdletBinding()] [OutputType([System.Boolean])] diff --git a/source/Public/Connect-SqlDscDatabaseEngine.ps1 b/source/Public/Connect-SqlDscDatabaseEngine.ps1 index ddccc99af..866ff2799 100644 --- a/source/Public/Connect-SqlDscDatabaseEngine.ps1 +++ b/source/Public/Connect-SqlDscDatabaseEngine.ps1 @@ -41,6 +41,9 @@ Connect-SqlDscDatabaseEngine -ServerName 'sql.company.local' -InstanceName 'MyInstance' Connects to the instance 'MyInstance' on the server 'sql.company.local'. + + .OUTPUTS + None. #> function Connect-SqlDscDatabaseEngine { diff --git a/source/Public/ConvertFrom-SqlDscDatabasePermission.ps1 b/source/Public/ConvertFrom-SqlDscDatabasePermission.ps1 index 99b4a2997..ad7b9aa97 100644 --- a/source/Public/ConvertFrom-SqlDscDatabasePermission.ps1 +++ b/source/Public/ConvertFrom-SqlDscDatabasePermission.ps1 @@ -6,14 +6,17 @@ .PARAMETER Permission Specifies a DatabasePermission object. - .OUTPUTS - [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] - .EXAMPLE [DatabasePermission] @{ State = 'Grant' Permission = 'Connect' } | ConvertFrom-SqlDscDatabasePermission + + Returns an object of `[Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]` + with all the permissions set to $true that was part of the `[DatabasePermission]`. + + .OUTPUTS + [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] #> function ConvertFrom-SqlDscDatabasePermission { diff --git a/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 b/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 index 68db8569a..093b8510c 100644 --- a/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 +++ b/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 @@ -7,13 +7,16 @@ Specifies a collection of Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo objects. - .OUTPUTS - [DatabasePermission[]] - .EXAMPLE $serverInstance = Connect-SqlDscDatabaseEngine $databasePermissionInfo = Get-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $databasePermissionInfo + + Get all permissions for the principal 'MyPrincipal' and converts the permissions + into an array of `[DatabasePermission[]]`. + + .OUTPUTS + [DatabasePermission[]] #> function ConvertTo-SqlDscDatabasePermission { diff --git a/source/Public/Get-SqlDscDatabasePermission.ps1 b/source/Public/Get-SqlDscDatabasePermission.ps1 index 84bf25fb7..8f9e3f49d 100644 --- a/source/Public/Get-SqlDscDatabasePermission.ps1 +++ b/source/Public/Get-SqlDscDatabasePermission.ps1 @@ -19,6 +19,8 @@ $serverInstance = Connect-SqlDscDatabaseEngine Get-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' + Get the permissions for the principal 'MyPrincipal'. + .NOTES This command excludes fixed roles like _db_datareader_ by default, and will always return `$null` if a fixed role is specified as **Name**. @@ -31,16 +33,11 @@ #> function Get-SqlDscDatabasePermission { - <# - The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error - in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 - When QA test run it loads the stub SMO classes so that the rule passes. - To get the rule to pass in the editor, in the Integrated Console run: - Add-Type -Path 'Tests/Unit/Stubs/SMO.cs' - #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because the rule does not understands that the command returns [System.String[]] when using , (comma) in the return statement')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] [CmdletBinding()] - [OutputType([System.Object[]])] + [OutputType([Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] @@ -94,5 +91,5 @@ function Get-SqlDscDatabasePermission Write-Error -Message $missingDatabaseMessage -Category 'InvalidOperation' -ErrorId 'GSDDP0002' -TargetObject $DatabaseName } - return , $getSqlDscDatabasePermissionResult + return , [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $getSqlDscDatabasePermissionResult } diff --git a/source/Public/Set-SqlDscDatabasePermission.ps1 b/source/Public/Set-SqlDscDatabasePermission.ps1 index be67e95da..99db1ae51 100644 --- a/source/Public/Set-SqlDscDatabasePermission.ps1 +++ b/source/Public/Set-SqlDscDatabasePermission.ps1 @@ -25,8 +25,8 @@ **State** is set to `Revoke` the right to grant will also be revoked, and the revocation will cascade. - .PARAMETER Permission - Specifies that the permissions will + .PARAMETER Force + Specifies that the permissions should be set with out any confirmation. .OUTPUTS None. @@ -41,6 +41,8 @@ Set-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' -State 'Grant' -Permission $setPermission + Sets the permissions for the principal 'MyPrincipal'. + .NOTES This command excludes fixed roles like _db_datareader_ by default, and will always throw a non-terminating error if a fixed role is specified as **Name**. @@ -52,13 +54,7 @@ #> function Set-SqlDscDatabasePermission { - <# - The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error - in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 - When QA test run it loads the stub SMO classes so that the rule passes. - To get the rule to pass in the editor, in the Integrated Console run: - Add-Type -Path 'Tests/Unit/Stubs/SMO.cs' - #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType()] diff --git a/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 b/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 index 99a4ff608..bba2d89a7 100644 --- a/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 +++ b/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 @@ -32,16 +32,35 @@ $serverInstance = Connect-SqlDscDatabaseEngine Test-SqlDscIsDatabasePrincipal -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' + Returns $true if the principal exist in the database, if not $false is returned. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Test-SqlDscIsDatabasePrincipal -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' -ExcludeUsers + + Returns $true if the principal exist in the database and is not a user, if not $false is returned. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Test-SqlDscIsDatabasePrincipal -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' -ExcludeRoles + + Returns $true if the principal exist in the database and is not a role, if not $false is returned. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Test-SqlDscIsDatabasePrincipal -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' -ExcludeFixedRoles + + Returns $true if the principal exist in the database and is not a fixed role, if not $false is returned. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Test-SqlDscIsDatabasePrincipal -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' -ExcludeApplicationRoles + + Returns $true if the principal exist in the database and is not a application role, if not $false is returned. #> function Test-SqlDscIsDatabasePrincipal { - <# - The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error - in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8 - When QA test run it loads the stub SMO classes so that the rule passes. - To get the rule to pass in the editor, in the Integrated Console run: - Add-Type -Path 'Tests/Unit/Stubs/SMO.cs' - #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [CmdletBinding()] [OutputType([System.Boolean])] param diff --git a/tests/Unit/Private/Get-ClassName.Tests.ps1 b/tests/Unit/Private/Get-ClassName.Tests.ps1 index 97306cbee..fa678b8fd 100644 --- a/tests/Unit/Private/Get-ClassName.Tests.ps1 +++ b/tests/Unit/Private/Get-ClassName.Tests.ps1 @@ -49,7 +49,7 @@ Describe 'Get-ClassName' -Tag 'Private' { InModuleScope -ScriptBlock { $result = Get-ClassName -InputObject ([System.UInt32] 3) - $result.GetType().FullName | Should -Be 'System.Object[]' + $result.GetType().FullName | Should -Be 'System.String[]' $result | Should -HaveCount 1 $result | Should -Contain 'System.UInt32' @@ -62,7 +62,7 @@ Describe 'Get-ClassName' -Tag 'Private' { InModuleScope -ScriptBlock { $result = ([System.UInt32] 3) | Get-ClassName - $result.GetType().FullName | Should -Be 'System.Object[]' + $result.GetType().FullName | Should -Be 'System.String[]' $result | Should -HaveCount 1 $result | Should -Contain 'System.UInt32' @@ -77,7 +77,7 @@ Describe 'Get-ClassName' -Tag 'Private' { InModuleScope -ScriptBlock { $result = Get-ClassName -InputObject ([System.UInt32] 3) -Recurse - $result.GetType().FullName | Should -Be 'System.Object[]' + $result.GetType().FullName | Should -Be 'System.String[]' $result | Should -HaveCount 2 $result | Should -Contain 'System.UInt32' @@ -94,7 +94,7 @@ Describe 'Get-ClassName' -Tag 'Private' { InModuleScope -ScriptBlock { $result = ([System.UInt32] 3) | Get-ClassName -Recurse - $result.GetType().FullName | Should -Be 'System.Object[]' + $result.GetType().FullName | Should -Be 'System.String[]' $result | Should -HaveCount 2 $result | Should -Contain 'System.UInt32' diff --git a/tests/Unit/Private/Test-ResourcePropertyIsAssigned.Tests.ps1 b/tests/Unit/Private/Test-ResourceDscPropertyIsAssigned.Tests.ps1 similarity index 92% rename from tests/Unit/Private/Test-ResourcePropertyIsAssigned.Tests.ps1 rename to tests/Unit/Private/Test-ResourceDscPropertyIsAssigned.Tests.ps1 index bf4659760..ffc3fa287 100644 --- a/tests/Unit/Private/Test-ResourcePropertyIsAssigned.Tests.ps1 +++ b/tests/Unit/Private/Test-ResourceDscPropertyIsAssigned.Tests.ps1 @@ -42,7 +42,7 @@ AfterAll { Get-Module -Name $script:dscModuleName -All | Remove-Module -Force } -Describe 'Test-ResourcePropertyIsAssigned' -Tag 'Private' { +Describe 'Test-ResourceDscPropertyIsAssigned' -Tag 'Private' { Context 'When DSC property has a non-null value' { BeforeAll { <# @@ -80,7 +80,7 @@ $script:mockResourceBaseInstance = [MyMockResource] @{ It 'Should return the correct value' { InModuleScope -ScriptBlock { - $result = Test-ResourcePropertyIsAssigned -Name 'MyProperty3' -InputObject $script:mockResourceBaseInstance + $result = Test-ResourceDscPropertyIsAssigned -Name 'MyProperty3' -InputObject $script:mockResourceBaseInstance $result | Should -BeTrue } @@ -123,7 +123,7 @@ $script:mockResourceBaseInstance = [MyMockResource] @{} It 'Should return the correct value' { InModuleScope -ScriptBlock { - $result = Test-ResourcePropertyIsAssigned -Name 'MyProperty3' -InputObject $script:mockResourceBaseInstance + $result = Test-ResourceDscPropertyIsAssigned -Name 'MyProperty3' -InputObject $script:mockResourceBaseInstance $result | Should -BeFalse } diff --git a/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 b/tests/Unit/Private/Test-ResourceHasDscProperty.Tests.ps1 similarity index 91% rename from tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 rename to tests/Unit/Private/Test-ResourceHasDscProperty.Tests.ps1 index 271a2e172..84ed3eb78 100644 --- a/tests/Unit/Private/Test-ResourceHasProperty.Tests.ps1 +++ b/tests/Unit/Private/Test-ResourceHasDscProperty.Tests.ps1 @@ -42,7 +42,7 @@ AfterAll { Get-Module -Name $script:dscModuleName -All | Remove-Module -Force } -Describe 'Test-ResourceHasProperty' -Tag 'Private' { +Describe 'Test-ResourceHasDscProperty' -Tag 'Private' { Context 'When resource does not have an Ensure property' { BeforeAll { <# @@ -78,7 +78,7 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() It 'Should return the correct value' { InModuleScope -ScriptBlock { - $result = Test-ResourceHasProperty -Name 'Ensure' -InputObject $script:mockResourceBaseInstance + $result = Test-ResourceHasDscProperty -Name 'Ensure' -InputObject $script:mockResourceBaseInstance $result | Should -BeFalse } @@ -120,7 +120,7 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() It 'Should return the correct value' { InModuleScope -ScriptBlock { - $result = Test-ResourceHasProperty -Name 'Ensure' -InputObject $script:mockResourceBaseInstance + $result = Test-ResourceHasDscProperty -Name 'Ensure' -InputObject $script:mockResourceBaseInstance $result | Should -BeTrue } @@ -161,7 +161,7 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() It 'Should return the correct value' { InModuleScope -ScriptBlock { - $result = Test-ResourceHasProperty -Name 'Ensure' -InputObject $script:mockResourceBaseInstance + $result = Test-ResourceHasDscProperty -Name 'Ensure' -InputObject $script:mockResourceBaseInstance $result | Should -BeFalse } @@ -206,7 +206,7 @@ $script:mockResourceBaseInstance = [MyMockResource] @{ It 'Should return the correct value' { InModuleScope -ScriptBlock { - $result = Test-ResourceHasProperty -Name 'MyProperty3' -HasValue -InputObject $script:mockResourceBaseInstance + $result = Test-ResourceHasDscProperty -Name 'MyProperty3' -HasValue -InputObject $script:mockResourceBaseInstance $result | Should -BeTrue } @@ -248,7 +248,7 @@ $script:mockResourceBaseInstance = [MyMockResource] @{} It 'Should return the correct value' { InModuleScope -ScriptBlock { - $result = Test-ResourceHasProperty -Name 'MyProperty3' -HasValue -InputObject $script:mockResourceBaseInstance + $result = Test-ResourceHasDscProperty -Name 'MyProperty3' -HasValue -InputObject $script:mockResourceBaseInstance $result | Should -BeFalse } diff --git a/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 b/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 index bbc9375c6..9ccc05e41 100644 --- a/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 +++ b/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 @@ -158,23 +158,29 @@ Describe 'Get-SqlDscDatabasePermission' -Tag 'Public' { $SqlServerLogin ) - $mockEnumDatabasePermissions = @() - - $mockEnumDatabasePermissions += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'Zebes\SamusAran' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockEnumDatabasePermissions += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'Zebes\SamusAran' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru + $mockEnumDatabasePermissions = [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] @() + + $mockEnumDatabasePermissions += [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo] @{ + PermissionType = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + PermissionState = 'Grant' + Grantee = 'Zebes\SamusAran' + GrantorType = 'User' + ObjectClass = 'DatabaseName' + ObjectName = 'AdventureWork' + } + + $mockEnumDatabasePermissions += [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo] @{ + PermissionType = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Update = $true + } + PermissionState = 'Grant' + Grantee = 'Zebes\SamusAran' + GrantorType = 'User' + ObjectClass = 'DatabaseName' + ObjectName = 'AdventureWork' + } return $mockEnumDatabasePermissions } -PassThru -Force diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index 5290b0a82..7bc18e797 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -202,23 +202,11 @@ public ServerPermissionInfo( // BaseType: Microsoft.SqlServer.Management.Smo.PermissionSetBase // Used by: // SqlDatabasePermission.Tests.ps1 + // Get-SqlDscDatabasePermission.Tests.ps1 public class DatabasePermissionSet { public DatabasePermissionSet(){} - public DatabasePermissionSet( bool connect, bool update ) - { - this.Connect = connect; - this.Update = update; - } - - // Used for testing SqlDatabasePermission - public DatabasePermissionSet( bool connect, bool update, bool select, bool insert ) : this ( connect, update ) - { - this.Select = select; - this.Insert = insert; - } - public bool Connect = false; public bool Update = false; public bool Select = false; @@ -232,6 +220,7 @@ public DatabasePermissionSet( bool connect, bool update, bool select, bool inser // BaseType: Microsoft.SqlServer.Management.Smo.PermissionInfo // Used by: // SqlDatabasePermission.Tests.ps1 + // Get-SqlDscDatabasePermission.Tests.ps1 public class DatabasePermissionInfo { public DatabasePermissionInfo() @@ -246,7 +235,11 @@ public DatabasePermissionInfo( Microsoft.SqlServer.Management.Smo.DatabasePermis } public Microsoft.SqlServer.Management.Smo.DatabasePermissionSet PermissionType; - public string PermissionState = "Grant"; + public string PermissionState; + public string Grantee; + public string GrantorType; + public string ObjectClass; + public string ObjectName; } // TypeName: Microsoft.SqlServer.Management.Smo.Server From ce11053e181581ccf4fe81911aa4c53f3bd9f045 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 29 Jul 2022 12:18:03 +0200 Subject: [PATCH 98/98] Fix remove built module from HQRM tests --- build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.yaml b/build.yaml index 650dd6932..40e6d57a3 100644 --- a/build.yaml +++ b/build.yaml @@ -119,6 +119,8 @@ DscTest: - output ExcludeModuleFile: - Modules/DscResource.Common + # Must exclude built module file because it should not be tested like MOF-based resources + - SqlServerDsc.psm1 MainGitBranch: main ####################################################