diff --git a/CHANGELOG.md b/CHANGELOG.md index a60cd41..cb26d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased - Added .VSCode settings for applying DSC PSSA rules - fixes [Issue #25](https://github.com/PlagueHO/FileContentDsc/issues/25). +- Added an Encoding parameter to the KeyValuePairFile and ReplaceText + resources - fixes [Issue #5](https://github.com/PlagueHO/FileContentDsc/issues/5). ## 1.1.0.0 diff --git a/DSCResources/DSR_KeyValuePairFile/DSR_KeyValuePairFile.psm1 b/DSCResources/DSR_KeyValuePairFile/DSR_KeyValuePairFile.psm1 index edf9d4d..2ac1f28 100644 --- a/DSCResources/DSR_KeyValuePairFile/DSR_KeyValuePairFile.psm1 +++ b/DSCResources/DSR_KeyValuePairFile/DSR_KeyValuePairFile.psm1 @@ -40,21 +40,22 @@ function Get-TargetResource ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Path, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Name ) Assert-ParametersValid @PSBoundParameters $fileContent = Get-Content -Path $Path -Raw + $fileEncoding = Get-FileEncoding -Path $Path Write-Verbose -Message ($localizedData.SearchForKeyMessage -f ` - $Path, $Name) + $Path, $Name) # Setup the Regex Options that will be used $regExOptions = [System.Text.RegularExpressions.RegexOptions]::Multiline @@ -69,7 +70,7 @@ function Get-TargetResource { # No matches found Write-Verbose -Message ($localizedData.KeyNotFoundMessage -f ` - $Path, $Name) + $Path, $Name) } else { @@ -85,12 +86,13 @@ function Get-TargetResource $text = ($textValues -join ',') Write-Verbose -Message ($localizedData.KeyFoundMessage -f ` - $Path, $Name, $text) + $Path, $Name, $text) } # if return @{ Path = $Path Name = $Name + Encoding = $fileEncoding Ensure = $ensure Type = 'Text' Text = $text @@ -129,6 +131,9 @@ function Get-TargetResource .PARAMETER IgnoreValueCase Ignore the case of any text or secret when determining if it they need to be updated. Defaults to $False. + + .PARAMETER Encoding + Specifies the file encoding. Defaults to ASCII. #> function Set-TargetResource { @@ -139,26 +144,26 @@ function Set-TargetResource ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Path, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Name, [Parameter()] [ValidateSet('Present', 'Absent')] - [String] + [System.String] $Ensure = 'Present', [Parameter()] [ValidateSet('Text', 'Secret')] - [String] + [System.String] $Type = 'Text', [Parameter()] - [String] + [System.String] $Text, [Parameter()] @@ -172,15 +177,27 @@ function Set-TargetResource [Parameter()] [System.Boolean] - $IgnoreValueCase = $false + $IgnoreValueCase = $false, + + [Parameter()] + [ValidateSet("ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32")] + [System.String] + $Encoding ) Assert-ParametersValid @PSBoundParameters $fileContent = Get-Content -Path $Path -Raw -ErrorAction SilentlyContinue + $fileEncoding = Get-FileEncoding -Path $Path + + $fileProperties = @{ + Path = $Path + NoNewline = $true + Force = $true + } Write-Verbose -Message ($localizedData.SearchForKeyMessage -f ` - $Path, $Name) + $Path, $Name) if ($Type -eq 'Secret') { @@ -218,7 +235,7 @@ function Set-TargetResource $fileContent += $keyValuePair Write-Verbose -Message ($localizedData.KeyAddMessage -f ` - $Path, $Name) + $Path, $Name) } else { @@ -226,15 +243,23 @@ function Set-TargetResource $fileContent = [regex]::Replace($fileContent, "^[\s]*$Name=(.*)($eolChars*)", $keyValuePair, $regExOptions) Write-Verbose -Message ($localizedData.KeyUpdateMessage -f ` - $Path, $Name) + $Path, $Name) } # if } else { if ($results.Count -eq 0) { - # The Key does not exists and should not so don't do anything - return + if ($PSBoundParameters.ContainsKey('Encoding') -and ($Encoding -eq $fileEncoding)) + { + # The Key does not exists and should not, and encoding is in the desired state, so don't do anything + return + } + else + { + Write-Verbose -Message ($localizedData.FileEncodingNotInDesiredState -f ` + $fileEncoding, $Encoding) + } } else { @@ -242,7 +267,7 @@ function Set-TargetResource $fileContent = [regex]::Replace($fileContent, "^[\s]*$Name=(.*)$eolChars", '', $regExOptions) Write-Verbose -Message ($localizedData.KeyRemoveMessage -f ` - $Path, $Name) + $Path, $Name) } } # if } @@ -251,11 +276,16 @@ function Set-TargetResource $fileContent = '{0}={1}' -f $Name, $Text } # if - Set-Content ` - -Path $Path ` - -Value $fileContent ` - -NoNewline ` - -Force + $fileProperties.Add('Value', $fileContent) + + # Verify encoding is not set to the passed parameter or the default of ASCII + if ($PSBoundParameters.ContainsKey('Encoding') -and ($Encoding -ne ($fileEncoding -or 'ASCII'))) + { + # Add encoding parameter and value to the hashtable + $fileProperties.Add('Encoding', $Encoding) + } + + Set-Content @fileProperties } <# @@ -288,6 +318,9 @@ function Set-TargetResource .PARAMETER IgnoreValueCase Ignore the case of any text or secret when determining if it they need to be updated. Defaults to $False. + + .PARAMETER Encoding + Specifies the file encoding. Defaults to ASCII. #> function Test-TargetResource { @@ -297,26 +330,26 @@ function Test-TargetResource ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Path, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Name, [Parameter()] [ValidateSet('Present', 'Absent')] - [String] + [System.String] $Ensure = 'Present', [Parameter()] [ValidateSet('Text', 'Secret')] - [String] + [System.String] $Type = 'Text', [Parameter()] - [String] + [System.String] $Text, [Parameter()] @@ -330,7 +363,12 @@ function Test-TargetResource [Parameter()] [System.Boolean] - $IgnoreValueCase = $false + $IgnoreValueCase = $false, + + [Parameter()] + [ValidateSet("ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32")] + [System.String] + $Encoding ) Assert-ParametersValid @PSBoundParameters @@ -345,9 +383,10 @@ function Test-TargetResource } $fileContent = Get-Content -Path $Path -Raw + $fileEncoding = Get-FileEncoding -Path $Path Write-Verbose -Message ($localizedData.SearchForKeyMessage -f ` - $Path, $Name) + $Path, $Name) # Setup the Regex Options that will be used $regExOptions = [System.Text.RegularExpressions.RegexOptions]::Multiline @@ -366,7 +405,7 @@ function Test-TargetResource { # The key value pairs should exist but do not Write-Verbose -Message ($localizedData.KeyNotFoundButShouldExistMessage -f ` - $Path, $Name) + $Path, $Name) $desiredConfigurationMatch = $false } @@ -374,12 +413,12 @@ function Test-TargetResource { # The key value pairs should exist and do Write-Verbose -Message ($localizedData.KeyNotFoundAndShouldNotExistMessage -f ` - $Path, $Name) + $Path, $Name) } # if } else { - # One of more key value pairs were found + # One or more key value pairs were found if ($Ensure -eq 'Present') { # The key value pairs should exist - but check values @@ -401,24 +440,28 @@ function Test-TargetResource if ($desiredConfigurationMatch) { Write-Verbose -Message ($localizedData.KeyFoundButNoReplacementMessage -f ` - $Path, $Name) + $Path, $Name) } - else - { - Write-Verbose -Message ($localizedData.KeyFoundReplacementRequiredMessage -f ` - $Path, $Name) - } # if } else { # The key value pairs should not exist Write-Verbose -Message ($localizedData.KeyFoundButShouldNotExistMessage -f ` - $Path, $Name) + $Path, $Name) $desiredConfigurationMatch = $false } # if } # if + if ($PSBoundParameters.ContainsKey('Encoding') -and ($Encoding -ne $fileEncoding)) + { + # File encoding is not in desired state + Write-Verbose -Message ($localizedData.FileEncodingNotInDesiredState -f ` + $fileEncoding, $Encoding) + + $desiredConfigurationMatch = $false + } + return $desiredConfigurationMatch } @@ -453,6 +496,9 @@ function Test-TargetResource .PARAMETER IgnoreValueCase Ignore the case of any text or secret when determining if it they need to be updated. Defaults to $False. + + .PARAMETER Encoding + Specifies the file encoding. Defaults to ASCII. #> function Assert-ParametersValid { @@ -461,26 +507,26 @@ function Assert-ParametersValid ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Path, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Name, [Parameter()] [ValidateSet('Present', 'Absent')] - [String] + [System.String] $Ensure = 'Present', [Parameter()] [ValidateSet('Text', 'Secret')] - [String] + [System.String] $Type = 'Text', [Parameter()] - [String] + [System.String] $Text, [Parameter()] @@ -494,7 +540,12 @@ function Assert-ParametersValid [Parameter()] [System.Boolean] - $IgnoreValueCase = $false + $IgnoreValueCase = $false, + + [Parameter()] + [ValidateSet("ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32")] + [System.String] + $Encoding ) # Does the file's parent path exist? diff --git a/DSCResources/DSR_KeyValuePairFile/DSR_KeyValuePairFile.schema.mof b/DSCResources/DSR_KeyValuePairFile/DSR_KeyValuePairFile.schema.mof index 05fee80..10a6110 100644 --- a/DSCResources/DSR_KeyValuePairFile/DSR_KeyValuePairFile.schema.mof +++ b/DSCResources/DSR_KeyValuePairFile/DSR_KeyValuePairFile.schema.mof @@ -9,4 +9,5 @@ class DSR_KeyValuePairFile : OMI_BaseResource [write, Description("The secret text to replace the value with in the identified key. Only used when Type is set to 'Secret'."),EmbeddedInstance("MSFT_Credential")] String Secret; [Write, Description("Ignore the case of the name of the key. Defaults to $False.")] Boolean IgnoreNameCase; [Write, Description("Ignore the case of any text or secret when determining if it they need to be updated. Defaults to $False.")] Boolean IgnoreValueCase; + [Write, Description("Specifies the file encoding. Defaults to ASCII"),ValueMap{"ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32"},Values{"ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32"}] String Encoding; }; diff --git a/DSCResources/DSR_KeyValuePairFile/en-US/DSR_KeyValuePairFile.strings.psd1 b/DSCResources/DSR_KeyValuePairFile/en-US/DSR_KeyValuePairFile.strings.psd1 index 7134505..5eafcdd 100644 --- a/DSCResources/DSR_KeyValuePairFile/en-US/DSR_KeyValuePairFile.strings.psd1 +++ b/DSCResources/DSR_KeyValuePairFile/en-US/DSR_KeyValuePairFile.strings.psd1 @@ -15,4 +15,5 @@ ConvertFrom-StringData @' KeyFoundReplacementRequiredMessage = Key '{1}' found in file '{0}' and should exist but value(s) are not correct. Change required. KeyFoundButShouldNotExistMessage = Key '{1}' found in file '{0}' but should not exist. Change required. FileParentNotFoundError = File parent path '{0}' not found. + FileEncodingNotInDesiredState = File encoding is set to '{0}' but should be set to '{1}', Change required. '@ diff --git a/DSCResources/DSR_ReplaceText/DSR_ReplaceText.psm1 b/DSCResources/DSR_ReplaceText/DSR_ReplaceText.psm1 index 96d6e90..269ff4b 100644 --- a/DSCResources/DSR_ReplaceText/DSR_ReplaceText.psm1 +++ b/DSCResources/DSR_ReplaceText/DSR_ReplaceText.psm1 @@ -40,21 +40,22 @@ function Get-TargetResource ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Path, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Search ) Assert-ParametersValid @PSBoundParameters $fileContent = Get-Content -Path $Path -Raw + $fileEncoding = Get-FileEncoding $Path Write-Verbose -Message ($localizedData.SearchForTextMessage -f ` - $Path, $Search) + $Path, $Search) $text = '' @@ -65,21 +66,22 @@ function Get-TargetResource { # No matches found - already in state Write-Verbose -Message ($localizedData.StringNotFoundMessage -f ` - $Path, $Search) + $Path, $Search) } else { $text = ($results.Value -join ',') Write-Verbose -Message ($localizedData.StringMatchFoundMessage -f ` - $Path, $Search, $text) + $Path, $Search, $text) } # if return @{ - Path = $Path - Search = $Search - Type = 'Text' - Text = $text + Path = $Path + Search = $Search + Type = 'Text' + Text = $text + Encoding = $fileEncoding } } @@ -106,6 +108,9 @@ function Get-TargetResource .PARAMETER AllowAppend Specifies to append text to the file being modified. Adds the ability to add a configuration entry. + + .PARAMETER Encoding + Specifies the file encoding. Defaults to ASCII. #> function Set-TargetResource { @@ -116,21 +121,21 @@ function Set-TargetResource ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Path, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Search, [Parameter()] [ValidateSet('Text', 'Secret')] - [String] + [System.String] $Type = 'Text', [Parameter()] - [String] + [System.String] $Text, [Parameter()] @@ -140,25 +145,48 @@ function Set-TargetResource [Parameter()] [System.Boolean] - $AllowAppend = $false + $AllowAppend = $false, + + [Parameter()] + [ValidateSet("ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32")] + [System.String] + $Encoding ) Assert-ParametersValid @PSBoundParameters $fileContent = Get-Content -Path $Path -Raw -ErrorAction SilentlyContinue + $fileEncoding = Get-FileEncoding $Path + + $fileProperties = @{ + Path = $Path + NoNewline = $true + Force = $true + } if ($Type -eq 'Secret') { Write-Verbose -Message ($localizedData.StringReplaceSecretMessage -f ` - $Path) + $Path) $Text = $Secret.GetNetworkCredential().Password } - else + elseif ($PSBoundParameters.ContainsKey('Encoding')) { - Write-Verbose -Message ($localizedData.StringReplaceTextMessage -f ` - $Path, $Text) - } # if + if ($Encoding -eq $fileEncoding) + { + Write-Verbose -Message ($localizedData.StringReplaceTextMessage -f ` + $Path, $Text) + } + else + { + Write-Verbose -Message ($localizedData.StringReplaceTextMessage -f ` + $Path, $Text) + + # Add encoding parameter and value to the hashtable + $fileProperties.Add('Encoding', $Encoding) + } + } if ($null -eq $fileContent) { @@ -176,11 +204,9 @@ function Set-TargetResource $fileContent = $fileContent -Replace $Search, $Text } - Set-Content ` - -Path $Path ` - -Value $fileContent ` - -NoNewline ` - -Force + $fileProperties.Add('Value', $fileContent) + + Set-Content @fileProperties } <# @@ -206,6 +232,9 @@ function Set-TargetResource .PARAMETER AllowAppend Specifies to append text to the file being modified. Adds the ability to add a configuration entry. + + .PARAMETER Encoding + Specifies the file encoding. Defaults to ASCII. #> function Test-TargetResource { @@ -215,21 +244,21 @@ function Test-TargetResource ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Path, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Search, [Parameter()] [ValidateSet('Text', 'Secret')] - [String] + [System.String] $Type = 'Text', [Parameter()] - [String] + [System.String] $Text, [Parameter()] @@ -239,21 +268,27 @@ function Test-TargetResource [Parameter()] [System.Boolean] - $AllowAppend = $false + $AllowAppend = $false, + + [Parameter()] + [ValidateSet("ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32")] + [System.String] + $Encoding ) Assert-ParametersValid @PSBoundParameters - # Check if file being managed exists. If not return $False. + # Check if file being managed exists. If not return $false. if (-not (Test-Path -Path $Path)) { return $false } $fileContent = Get-Content -Path $Path -Raw + $fileEncoding = Get-FileEncoding $Path Write-Verbose -Message ($localizedData.SearchForTextMessage -f ` - $Path, $Search) + $Path, $Search) # Search the file content for any matches $results = [regex]::Matches($fileContent, $Search) @@ -268,12 +303,25 @@ function Test-TargetResource return $false } - - # No matches found - already in state - Write-Verbose -Message ($localizedData.StringNotFoundMessage -f ` + if ($PSBoundParameters.ContainsKey('Encoding')) + { + if ($Encoding -eq $fileEncoding) + { + # No matches found and encoding is in desired state + Write-Verbose -Message ($localizedData.StringNotFoundMessage -f ` $Path, $Search) - return $true + return $true + } + else + { + # No matches found but encoding is not in desired state + Write-Verbose -Message ($localizedData.FileEncodingNotInDesiredState -f ` + $fileEncoding, $Encoding) + + return $false + } + } } # Flag to signal whether settings are correct @@ -295,12 +343,12 @@ function Test-TargetResource if ($desiredConfigurationMatch) { Write-Verbose -Message ($localizedData.StringNoReplacementMessage -f ` - $Path, $Search) + $Path, $Search) } else { Write-Verbose -Message ($localizedData.StringReplacementRequiredMessage -f ` - $Path, $Search) + $Path, $Search) } # if return $desiredConfigurationMatch @@ -327,6 +375,9 @@ function Test-TargetResource .PARAMETER Secret The secret text to replace the text identified by the RegEx. Only used when Type is set to 'Secret'. + + .PARAMETER Encoding + Specifies the file encoding. Defaults to ASCII. #> function Assert-ParametersValid { @@ -335,21 +386,21 @@ function Assert-ParametersValid ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Path, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $Search, [Parameter()] [ValidateSet('Text', 'Secret')] - [String] + [System.String] $Type = 'Text', [Parameter()] - [String] + [System.String] $Text, [Parameter()] @@ -359,7 +410,12 @@ function Assert-ParametersValid [Parameter()] [System.Boolean] - $AllowAppend = $false + $AllowAppend = $false, + + [Parameter()] + [ValidateSet("ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32")] + [System.String] + $Encoding ) # Does the file's parent path exist? @@ -389,11 +445,11 @@ function Add-ConfigurationEntry param ( [Parameter(Mandatory = $true)] - [String] + [System.String] $FileContent, [Parameter(Mandatory = $true)] - [String] + [System.String] $Text ) diff --git a/DSCResources/DSR_ReplaceText/DSR_ReplaceText.schema.mof b/DSCResources/DSR_ReplaceText/DSR_ReplaceText.schema.mof index c30aaa4..c1ebb23 100644 --- a/DSCResources/DSR_ReplaceText/DSR_ReplaceText.schema.mof +++ b/DSCResources/DSR_ReplaceText/DSR_ReplaceText.schema.mof @@ -7,4 +7,5 @@ class DSR_ReplaceText : OMI_BaseResource [Write, Description("The text to replace the text identified by the RegEx. Only used when Type is set to 'Text'.")] String Text; [Write, Description("The secret text to replace the text identified by the RegEx. Only used when Type is set to 'Secret'."),EmbeddedInstance("MSFT_Credential")] String Secret; [Write, Description("Specifies to append text to the file being modified. Adds the ability to add a configuration entry.")] Boolean AllowAppend; + [Write, Description("Specifies the file encoding. Defaults to ASCII"),ValueMap{"ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32"},Values{"ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32"}] String Encoding; }; diff --git a/DSCResources/DSR_ReplaceText/en-US/DSR_ReplaceText.strings.psd1 b/DSCResources/DSR_ReplaceText/en-US/DSR_ReplaceText.strings.psd1 index 98ee192..c99f0d9 100644 --- a/DSCResources/DSR_ReplaceText/en-US/DSR_ReplaceText.strings.psd1 +++ b/DSCResources/DSR_ReplaceText/en-US/DSR_ReplaceText.strings.psd1 @@ -10,4 +10,5 @@ ConvertFrom-StringData @' StringReplaceTextMessage = String replaced by '{1}' in file '{0}'. StringReplaceSecretMessage = String replaced by secret text in file '{0}'. FileParentNotFoundError = File parent path '{0}' not found. + FileEncodingNotInDesiredState = File encoding is set to '{0}' but should be set to '{1}', Change required. '@ diff --git a/Modules/FileContentDsc.Common/FileContentDsc.Common.psm1 b/Modules/FileContentDsc.Common/FileContentDsc.Common.psm1 index 4507376..c90f986 100644 --- a/Modules/FileContentDsc.Common/FileContentDsc.Common.psm1 +++ b/Modules/FileContentDsc.Common/FileContentDsc.Common.psm1 @@ -146,4 +146,55 @@ function Get-IniSettingFileValue Return [IniFile]::GetIniSetting($fullPath, $Section, $Key, '') } +<# + .SYNOPSIS + Gets file encoding. Defaults to ASCII. + + .DESCRIPTION + The Get-FileEncoding function determines encoding by looking at Byte Order Mark (BOM). + Based on port of C# code from http://www.west-wind.com/Weblog/posts/197245.aspx + + .EXAMPLE + Get-ChildItem *.ps1 | select FullName, @{n='Encoding';e={Get-FileEncoding $_.FullName}} | where {$_.Encoding -ne 'ASCII'} + This command gets ps1 files in current directory where encoding is not ASCII + + .EXAMPLE + Get-ChildItem *.ps1 | select FullName, @{n='Encoding';e={Get-FileEncoding $_.FullName}} | where {$_.Encoding -ne 'ASCII'} | ` + foreach {(get-content $_.FullName) | set-content $_.FullName -Encoding ASCII} + Same as previous example but fixes encoding using set-content +#> +function Get-FileEncoding +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] + [System.String] + $Path + ) + + [byte[]]$byte = Get-Content -Encoding byte -ReadCount 4 -TotalCount 4 -Path $Path + + if ($byte[0] -eq 0xef -and $byte[1] -eq 0xbb -and $byte[2] -eq 0xbf) + { + return 'UTF8' + } + elseif ($byte[0] -eq 0xff -and $byte[1] -eq 0xfe) + { + return 'UTF32' + } + elseif ($byte[0] -eq 0xfe -and $byte[1] -eq 0xff) + { + return 'BigEndianUnicode' + } + elseif ($byte[0] -eq 0 -and $byte[1] -eq 0 -and $byte[2] -eq 0xfe -and $byte[3] -eq 0xff) + { + return 'BigEndianUTF32' + } + else + { + return 'ASCII' + } +} + Export-ModuleMember -Function * diff --git a/README.md b/README.md index 0740b7a..4abab6b 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ The **FileContent** module contains the following resources: - **IniSettingsFile**: Add, set or clear entries in Windows INI settings files. - **KeyValuePairFile**: Add, remove or set key/value pairs in a text file containing - key/value pairs. -- **ReplaceText**: Replaces strings matching a regular expression in a file. + key/value pairs, and set file encoding. +- **ReplaceText**: Replaces strings matching a regular expression in a file, + and sets file encoding. **This project is not maintained or supported by Microsoft.** diff --git a/Tests/Integration/DSR_KeyValuePairFile.Integration.Tests.ps1 b/Tests/Integration/DSR_KeyValuePairFile.Integration.Tests.ps1 index cb7b640..ce658ea 100644 --- a/Tests/Integration/DSR_KeyValuePairFile.Integration.Tests.ps1 +++ b/Tests/Integration/DSR_KeyValuePairFile.Integration.Tests.ps1 @@ -16,6 +16,8 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force Import-Module (Join-Path -Path $script:moduleRoot -ChildPath "$($script:DSCModuleName).psd1") -Force +# Helper module import required for the Get-FileEncoding function +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Modules\FileContentDsc.Common') -Force $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` @@ -34,6 +36,16 @@ try $script:testSecureSecret = ConvertTo-SecureString -String $script:testSecret -AsPlainText -Force $script:testSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ('Dummy', $script:testSecureSecret) + $script:fileEncodingParameters = @{ + Path = $script:testTextFile + Encoding = 'ASCII' + } + + $script:testNonCompliantEncoding = @{ + Path = $script:fileEncodingParameters.Path + Encoding = 'UTF8' + } + $script:testFileContent = @" Setting1=Value1 $($script:testName)=Value 2 @@ -269,6 +281,76 @@ Setting3.Test=Value4 } } } + + Context 'A text file that requires encoding be changed' { + BeforeAll { + # Create the text file to use for testing + Set-Content ` + -Path $script:testTextFile ` + -Value $script:testFileContent ` + -Encoding $script:testNonCompliantEncoding.Encoding ` + -NoNewline ` + -Force + } + + #region DEFAULT TESTS + It 'Should compile and apply the MOF without throwing' { + { + $configData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + Path = $script:testTextFile + Name = $script:testName + Ensure = 'Present' + Type = 'Text' + Text = $script:testText + Encoding = $script:fileEncodingParameters.Encoding + } + ) + } + + & $script:configurationName ` + -OutputPath $TestDrive ` + -ConfigurationData $configData + + Start-DscConfiguration ` + -Path $TestDrive ` + -ComputerName localhost ` + -Wait ` + -Verbose ` + -Force ` + -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw + } + + It 'Should have set the resource and all the parameters should match' { + $script:current = $script:currentDscConfig | Where-Object { + $_.ConfigurationName -eq $script:configurationName + } + $current.Path | Should -Be $script:testTextFile + $current.Name | Should -Be $script:testName + $current.Ensure | Should -Be 'Present' + $current.Type | Should -Be 'Text' + $current.Text | Should -Be "$($script:testText),$($script:testText),$($script:testText)" + $current.Encoding | Should -Be $script:fileEncodingParameters.Encoding + } + + It 'Should convert file encoding to the expected type' { + Get-FileEncoding -Path $script:testTextFile | Should -Be $script:fileEncodingParameters.Encoding + } + + AfterAll { + if (Test-Path -Path $script:testTextFile) + { + Remove-Item -Path $script:testTextFile -Force + } + } + } } } finally diff --git a/Tests/Integration/DSR_KeyValuePairFile.config.ps1 b/Tests/Integration/DSR_KeyValuePairFile.config.ps1 index a30af36..b7db524 100644 --- a/Tests/Integration/DSR_KeyValuePairFile.config.ps1 +++ b/Tests/Integration/DSR_KeyValuePairFile.config.ps1 @@ -30,6 +30,18 @@ Configuration $ConfigurationName Text = $Node.Text } } + elseif (($Node.Type -eq 'Text') -and ($Node.Encoding -eq 'ASCII')) + { + KeyValuePairFile KeyValuePairFileIntegrationTest + { + Path = $Node.Path + Name = $Node.Name + Ensure = $Node.Ensure + Type = $Node.Type + Text = $Node.Text + Encoding = $Node.Encoding + } + } else { KeyValuePairFile KeyValuePairFileIntegrationTest diff --git a/Tests/Integration/DSR_ReplaceText.Integration.Tests.ps1 b/Tests/Integration/DSR_ReplaceText.Integration.Tests.ps1 index acf9be0..ebfdc2f 100644 --- a/Tests/Integration/DSR_ReplaceText.Integration.Tests.ps1 +++ b/Tests/Integration/DSR_ReplaceText.Integration.Tests.ps1 @@ -16,6 +16,8 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force Import-Module (Join-Path -Path $script:moduleRoot -ChildPath "$($script:DSCModuleName).psd1") -Force +# Helper module import required for the Get-FileEncoding function +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Modules\FileContentDsc.Common') -Force $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` @@ -36,6 +38,16 @@ try $script:testSecureSecretReplace = ConvertTo-SecureString -String $script:testSecretReplace -AsPlainText -Force $script:testSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ('Dummy', $script:testSecureSecretReplace) + $script:fileEncodingParameters = @{ + Path = $script:testTextFile + Encoding = 'ASCII' + } + + $script:testNonCompliantEncoding = @{ + Path = $script:fileEncodingParameters.Path + Encoding = 'UTF8' + } + $script:testFileContent = @" Setting1=Value1 Setting.Two='Value2' @@ -196,6 +208,74 @@ Setting3.Test=Value4 } } } + + Context 'A text file that requires encoding be changed' { + BeforeAll { + # Create the text file to use for testing + Set-Content ` + -Path $script:testTextFile ` + -Value $script:testFileContent ` + -Encoding $script:testNonCompliantEncoding.Encoding ` + -NoNewline ` + -Force + } + + #region DEFAULT TESTS + It 'Should compile and apply the MOF without throwing' { + { + $configData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + Path = $script:testTextFile + Search = $script:testSearch + Type = 'Text' + Text = $script:testTextReplace + Encoding = $script:fileEncodingParameters.Encoding + } + ) + } + + & $script:configurationName ` + -OutputPath $TestDrive ` + -ConfigurationData $configData + + Start-DscConfiguration ` + -Path $TestDrive ` + -ComputerName localhost ` + -Wait ` + -Verbose ` + -Force ` + -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw + } + + It 'Should have set the resource and all the parameters should match' { + $script:current = $script:currentDscConfig | Where-Object { + $_.ConfigurationName -eq $script:configurationName + } + $current.Path | Should -Be $script:testTextFile + $current.Search | Should -Be $script:testSearch + $current.Type | Should -Be 'Text' + $current.Text | Should -Be "$($script:testTextReplace),$($script:testTextReplace),$($script:testTextReplace)" + $current.Encoding | Should -Be $script:fileEncodingParameters.Encoding + } + + It 'Should convert file encoding to the expected type' { + Get-FileEncoding -Path $script:testTextFile | Should -Be $script:fileEncodingParameters.Encoding + } + + AfterAll { + if (Test-Path -Path $script:testTextFile) + { + Remove-Item -Path $script:testTextFile -Force + } + } + } } } finally diff --git a/Tests/Integration/DSR_ReplaceText.config.ps1 b/Tests/Integration/DSR_ReplaceText.config.ps1 index 4639099..4fc4141 100644 --- a/Tests/Integration/DSR_ReplaceText.config.ps1 +++ b/Tests/Integration/DSR_ReplaceText.config.ps1 @@ -20,6 +20,17 @@ Configuration $ConfigurationName Text = $Node.Text } } + elseif (($Node.Type -eq 'Text') -and ($Node.Encoding -eq 'ASCII')) + { + ReplaceText ReplaceTextIntegrationTest + { + Path = $Node.Path + Search = $Node.Search + Type = $Node.Type + Text = $Node.Text + Encoding = $Node.Encoding + } + } else { ReplaceText ReplaceTextIntegrationTest diff --git a/Tests/Unit/DSR_KeyValuePairFile.Tests.ps1 b/Tests/Unit/DSR_KeyValuePairFile.Tests.ps1 index f700346..c9f1dc8 100644 --- a/Tests/Unit/DSR_KeyValuePairFile.Tests.ps1 +++ b/Tests/Unit/DSR_KeyValuePairFile.Tests.ps1 @@ -40,6 +40,21 @@ try $script:testSecureSecret = ConvertTo-SecureString -String $script:testSecret -AsPlainText -Force $script:testSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ('Dummy', $script:testSecureSecret) + $script:fileEncodingParameters = @{ + Path = $script:testTextFile + Encoding = 'ASCII' + } + + $script:testCompliantEncoding = @{ + Path = $script:fileEncodingParameters.Path + Encoding = $script:fileEncodingParameters.Encoding + } + + $script:testNonCompliantEncoding = @{ + Path = $script:fileEncodingParameters.Path + Encoding = 'UTF8' + } + $script:testFileContent = @" Setting1=Value1 $($script:testName)=Value 2 @@ -109,6 +124,12 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileExpectedTextContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + $script:result = $null It 'Should not throw an exception' { @@ -151,6 +172,12 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileExpectedTextContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + $script:result = $null It 'Should not throw an exception' { @@ -197,6 +224,12 @@ $($script:testAddedName)=$($script:testText) -MockWith { $null } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + Mock ` -CommandName Set-Content ` -ParameterFilter { @@ -247,6 +280,12 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + Mock ` -CommandName Set-Content ` -ParameterFilter { @@ -297,6 +336,12 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + Mock ` -CommandName Set-Content ` -ParameterFilter { @@ -348,6 +393,12 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + Mock ` -CommandName Set-Content ` -ParameterFilter { @@ -398,6 +449,12 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + Mock ` -CommandName Set-Content ` -ParameterFilter { @@ -449,6 +506,12 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + # non-verifiable mocks Mock ` -CommandName Set-Content @@ -457,6 +520,7 @@ $($script:testAddedName)=$($script:testText) { Set-TargetResource ` -Path $script:testTextFile ` -Name $script:testName.ToUpper() ` + -Encoding $script:fileEncodingParameters.Encoding ` -Ensure 'Absent' ` -Verbose } | Should -Not -Throw @@ -490,6 +554,12 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + Mock ` -CommandName Set-Content ` -ParameterFilter { @@ -550,6 +620,12 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + It 'Should not throw an exception' { { $script:result = Test-TargetResource ` -Path $script:testTextFile ` @@ -575,7 +651,7 @@ $($script:testAddedName)=$($script:testText) } } - Context 'File exists and does not contain matching key but should not' { + Context 'File exists and does not contain matching key and should not' { # verifiable (should be called) mocks Mock ` -CommandName Assert-ParametersValid ` @@ -594,11 +670,18 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + It 'Should not throw an exception' { { $script:result = Test-TargetResource ` -Path $script:testTextFile ` -Name $script:testName.ToUpper() ` -Ensure 'Absent' ` + -Encoding $script:fileEncodingParameters.Encoding ` -Verbose } | Should -Not -Throw } @@ -618,6 +701,56 @@ $($script:testAddedName)=$($script:testText) } } + Context 'File exists and does not contain matching key and should not but encoding is not in desired state' { + # verifiable (should be called) mocks + Mock ` + -CommandName Assert-ParametersValid ` + -ModuleName 'DSR_KeyValuePairFile' ` + -Verifiable + + Mock ` + -CommandName Test-Path ` + -ModuleName 'DSR_KeyValuePairFile' ` + -MockWith { $true } ` + -Verifiable + + Mock ` + -CommandName Get-Content ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testFileContent } ` + -Verifiable + + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testNonCompliantEncoding.Encoding } ` + -Verifiable + + It 'Should not throw an exception' { + { $script:result = Test-TargetResource ` + -Path $script:testTextFile ` + -Name $script:testName.ToUpper() ` + -Encoding $script:fileEncodingParameters.Encoding ` + -Ensure 'Absent' ` + -Verbose + } | Should -Not -Throw + } + + It 'Should return false' { + $script:result | Should -Be $false + } + + It 'Should call the expected mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Assert-ParametersValid -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-Content ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 + } + } + Context 'File exists and contains matching key that should exist and values match' { # verifiable (should be called) mocks Mock ` @@ -637,6 +770,12 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileExpectedTextContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + It 'Should not throw an exception' { { $script:result = Test-TargetResource ` -Path $script:testTextFile ` @@ -662,7 +801,7 @@ $($script:testAddedName)=$($script:testText) } } - Context 'File exists and contains matching key that should exist and values do not match secret text' { + Context 'File exists and contains matching key that should exist and values match but encoding is not in desired state' { # verifiable (should be called) mocks Mock ` -CommandName Assert-ParametersValid ` @@ -678,7 +817,13 @@ $($script:testAddedName)=$($script:testText) Mock ` -CommandName Get-Content ` -ParameterFilter { $path -eq $script:testTextFile } ` - -MockWith { $script:testFileContent } ` + -MockWith { $script:testFileExpectedTextContent } ` + -Verifiable + + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testNonCompliantEncoding.Encoding } ` -Verifiable It 'Should not throw an exception' { @@ -686,8 +831,8 @@ $($script:testAddedName)=$($script:testText) -Path $script:testTextFile ` -Name $script:testName ` -Ensure 'Present' ` - -Type 'Secret' ` - -Secret $script:testSecretCredential ` + -Text $script:testText ` + -Encoding $script:fileEncodingParameters.Encoding ` -Verbose } | Should -Not -Throw } @@ -707,7 +852,7 @@ $($script:testAddedName)=$($script:testText) } } - Context 'File exists and contains matching key that should exist and values match secret text' { + Context 'File exists and contains matching key that should exist and values do not match secret text' { # verifiable (should be called) mocks Mock ` -CommandName Assert-ParametersValid ` @@ -723,7 +868,13 @@ $($script:testAddedName)=$($script:testText) Mock ` -CommandName Get-Content ` -ParameterFilter { $path -eq $script:testTextFile } ` - -MockWith { $script:testFileExpectedSecretContent } ` + -MockWith { $script:testFileContent } ` + -Verifiable + + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` -Verifiable It 'Should not throw an exception' { @@ -737,8 +888,8 @@ $($script:testAddedName)=$($script:testText) } | Should -Not -Throw } - It 'Should return true' { - $script:result | Should -Be $true + It 'Should return false' { + $script:result | Should -Be $false } It 'Should call the expected mocks' { @@ -752,7 +903,7 @@ $($script:testAddedName)=$($script:testText) } } - Context 'File exists and contains key with different case that should exist and values match and IgnoreNameCase is True' { + Context 'File exists and contains matching key that should exist and values match secret text' { # verifiable (should be called) mocks Mock ` -CommandName Assert-ParametersValid ` @@ -768,16 +919,22 @@ $($script:testAddedName)=$($script:testText) Mock ` -CommandName Get-Content ` -ParameterFilter { $path -eq $script:testTextFile } ` - -MockWith { $script:testFileExpectedTextContent } ` + -MockWith { $script:testFileExpectedSecretContent } ` + -Verifiable + + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` -Verifiable It 'Should not throw an exception' { { $script:result = Test-TargetResource ` -Path $script:testTextFile ` - -Name $script:testName.ToUpper() ` + -Name $script:testName ` -Ensure 'Present' ` - -Text $script:testText ` - -IgnoreNameCase:$true ` + -Type 'Secret' ` + -Secret $script:testSecretCredential ` -Verbose } | Should -Not -Throw } @@ -797,7 +954,7 @@ $($script:testAddedName)=$($script:testText) } } - Context 'File exists and contains matching key that should exist and values match but are different case and IgnoreValueCase is False' { + Context 'File exists and contains matching key that should exist and values match secret text but encoding is not in desired state' { # verifiable (should be called) mocks Mock ` -CommandName Assert-ParametersValid ` @@ -813,7 +970,13 @@ $($script:testAddedName)=$($script:testText) Mock ` -CommandName Get-Content ` -ParameterFilter { $path -eq $script:testTextFile } ` - -MockWith { $script:testFileExpectedTextContent } ` + -MockWith { $script:testFileExpectedSecretContent } ` + -Verifiable + + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testNonCompliantEncoding.Encoding } ` -Verifiable It 'Should not throw an exception' { @@ -821,7 +984,9 @@ $($script:testAddedName)=$($script:testText) -Path $script:testTextFile ` -Name $script:testName ` -Ensure 'Present' ` - -Text $script:testText.ToUpper() ` + -Type 'Secret' ` + -Secret $script:testSecretCredential ` + -Encoding $script:fileEncodingParameters.Encoding ` -Verbose } | Should -Not -Throw } @@ -841,7 +1006,7 @@ $($script:testAddedName)=$($script:testText) } } - Context 'File exists and contains matching key that should exist and values match but are different case and IgnoreValueCase is True' { + Context 'File exists and contains key with different case that should exist and values match and IgnoreNameCase is True' { # verifiable (should be called) mocks Mock ` -CommandName Assert-ParametersValid ` @@ -860,14 +1025,20 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileExpectedTextContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + It 'Should not throw an exception' { { $script:result = Test-TargetResource ` - -Path $script:testTextFile ` - -Name $script:testName ` - -Ensure 'Present' ` - -Text $script:testText.ToUpper() ` - -IgnoreValueCase:$true ` - -Verbose + -Path $script:testTextFile ` + -Name $script:testName.ToUpper() ` + -Ensure 'Present' ` + -Text $script:testText ` + -IgnoreNameCase:$true ` + -Verbose } | Should -Not -Throw } @@ -886,7 +1057,7 @@ $($script:testAddedName)=$($script:testText) } } - Context 'File exists and contains matching key that should not exist' { + Context 'File exists and contains matching key that should exist and values match but are different case and IgnoreValueCase is False' { # verifiable (should be called) mocks Mock ` -CommandName Assert-ParametersValid ` @@ -905,12 +1076,18 @@ $($script:testAddedName)=$($script:testText) -MockWith { $script:testFileExpectedTextContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + It 'Should not throw an exception' { { $script:result = Test-TargetResource ` -Path $script:testTextFile ` -Name $script:testName ` - -Ensure 'Absent' ` - -Text $script:testText ` + -Ensure 'Present' ` + -Text $script:testText.ToUpper() ` -Verbose } | Should -Not -Throw } @@ -930,7 +1107,7 @@ $($script:testAddedName)=$($script:testText) } } - Context 'File exists and does not contain matching key but it should' { + Context 'File exists and contains matching key that should exist and values match but are different case and IgnoreValueCase is True' { # verifiable (should be called) mocks Mock ` -CommandName Assert-ParametersValid ` @@ -940,14 +1117,77 @@ $($script:testAddedName)=$($script:testText) Mock ` -CommandName Test-Path ` -ModuleName 'DSR_KeyValuePairFile' ` - -MockWith { $false } ` + -MockWith { $true } ` + -Verifiable + + Mock ` + -CommandName Get-Content ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testFileExpectedTextContent } ` + -Verifiable + + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + + It 'Should not throw an exception' { + { $script:result = Test-TargetResource ` + -Path $script:testTextFile ` + -Name $script:testName ` + -Ensure 'Present' ` + -Text $script:testText.ToUpper() ` + -IgnoreValueCase:$true ` + -Verbose + } | Should -Not -Throw + } + + It 'Should return true' { + $script:result | Should -Be $true + } + + It 'Should call the expected mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Assert-ParametersValid -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-Content ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 + } + } + + Context 'File exists and contains matching key that should not exist' { + # verifiable (should be called) mocks + Mock ` + -CommandName Assert-ParametersValid ` + -ModuleName 'DSR_KeyValuePairFile' ` + -Verifiable + + Mock ` + -CommandName Test-Path ` + -ModuleName 'DSR_KeyValuePairFile' ` + -MockWith { $true } ` + -Verifiable + + Mock ` + -CommandName Get-Content ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testFileExpectedTextContent } ` + -Verifiable + + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` -Verifiable It 'Should not throw an exception' { { $script:result = Test-TargetResource ` -Path $script:testTextFile ` - -Name $script:testName.ToUpper() ` - -Ensure 'Present' ` + -Name $script:testName ` + -Ensure 'Absent' ` -Text $script:testText ` -Verbose } | Should -Not -Throw @@ -960,9 +1200,13 @@ $($script:testAddedName)=$($script:testText) It 'Should call the expected mocks' { Assert-VerifiableMock Assert-MockCalled -CommandName Assert-ParametersValid -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-Content ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 } } - } #endregion diff --git a/Tests/Unit/DSR_ReplaceText.Tests.ps1 b/Tests/Unit/DSR_ReplaceText.Tests.ps1 index cd8c723..83ddbfc 100644 --- a/Tests/Unit/DSR_ReplaceText.Tests.ps1 +++ b/Tests/Unit/DSR_ReplaceText.Tests.ps1 @@ -42,6 +42,21 @@ try $script:testSecureSecretReplace = ConvertTo-SecureString -String $script:testSecretReplace -AsPlainText -Force $script:testSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ('Dummy', $script:testSecureSecretReplace) + $script:fileEncodingParameters = @{ + Path = $script:testTextFile + Encoding = 'ASCII' + } + + $script:testCompliantEncoding = @{ + Path = $script:fileEncodingParameters.Path + Encoding = $script:fileEncodingParameters.Encoding + } + + $script:testNonCompliantEncoding = @{ + Path = $script:fileEncodingParameters.Path + Encoding = 'UTF8' + } + $script:testFileContent = @" Setting1=Value1 Setting.Two='Value2' @@ -82,7 +97,7 @@ Setting3.Test=Value4 #region Function Get-TargetResource Describe 'DSR_ReplaceText\Get-TargetResource' { - Context 'File exists and search text can be found' { + Context 'File exists and search text can be found and encoding is in desired state' { # verifiable (should be called) mocks Mock ` -CommandName Assert-ParametersValid ` @@ -95,13 +110,72 @@ Setting3.Test=Value4 -MockWith { $script:testFileExpectedTextContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + $script:result = $null It 'Should not throw an exception' { { $script:result = Get-TargetResource ` - -Path $script:testTextFile ` - -Search $script:testSearch ` - -Verbose + -Path $script:testTextFile ` + -Search $script:testSearch ` + -Verbose + } | Should -Not -Throw + } + + It 'Should return expected values' { + $script:result.Path | Should -Be $script:testTextFile + $script:result.Search | Should -Be $script:testSearch + $script:result.Type | Should -Be 'Text' + $script:result.Text | Should -Be "$($script:testTextReplace),$($script:testTextReplace),$($script:testTextReplace)" + $script:result.Encoding | Should -Be $script:testCompliantEncoding.Encoding + } + + It 'Should call the expected mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Assert-ParametersValid -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-Content ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 + } + } + + Context 'File exists and search text can be found but encoding is not in desired state' { + # verifiable (should be called) mocks + Mock ` + -CommandName Assert-ParametersValid ` + -ModuleName 'DSR_ReplaceText' ` + -Verifiable + + Mock ` + -CommandName Get-Content ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testFileExpectedTextContent } ` + -Verifiable + + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testNonCompliantEncoding.Encoding } ` + -Verifiable + + $script:result = $null + + It 'Should not throw an exception' { + { $script:result = Get-TargetResource ` + -Path $script:testTextFile ` + -Search $script:testSearch ` + -Verbose } | Should -Not -Throw } @@ -110,6 +184,7 @@ Setting3.Test=Value4 $script:result.Search | Should -Be $script:testSearch $script:result.Type | Should -Be 'Text' $script:result.Text | Should -Be "$($script:testTextReplace),$($script:testTextReplace),$($script:testTextReplace)" + $script:result.Encoding | Should -Be $script:testNonCompliantEncoding.Encoding } It 'Should call the expected mocks' { @@ -120,10 +195,15 @@ Setting3.Test=Value4 -CommandName Get-Content ` -ParameterFilter { $path -eq $script:testTextFile } ` -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 } } - Context 'File exists and search text can not be found' { + Context 'File exists and search text can not be found and encoding is in desired state' { # verifiable (should be called) mocks Mock ` -CommandName Assert-ParametersValid ` @@ -136,6 +216,12 @@ Setting3.Test=Value4 -MockWith { $script:testFileExpectedTextContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + $script:result = $null It 'Should not throw an exception' { @@ -151,6 +237,7 @@ Setting3.Test=Value4 $script:result.Search | Should -Be $script:testSearchNoFind $script:result.Type | Should -Be 'Text' $script:result.Text | Should -BeNullOrEmpty + $script:result.Encoding | Should -Be $script:fileEncodingParameters.Encoding } It 'Should call the expected mocks' { @@ -161,6 +248,64 @@ Setting3.Test=Value4 -CommandName Get-Content ` -ParameterFilter { $path -eq $script:testTextFile } ` -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 + } + } + + Context 'File exists and search text can not be found but encoding is not in desired state' { + # verifiable (should be called) mocks + Mock ` + -CommandName Assert-ParametersValid ` + -ModuleName 'DSR_ReplaceText' ` + -Verifiable + + Mock ` + -CommandName Get-Content ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testFileExpectedTextContent } ` + -Verifiable + + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testNonCompliantEncoding.Encoding } ` + -Verifiable + + $script:result = $null + + It 'Should not throw an exception' { + { $script:result = Get-TargetResource ` + -Path $script:testTextFile ` + -Search $script:testSearchNoFind ` + -Verbose + } | Should -Not -Throw + } + + It 'Should return expected values' { + $script:result.Path | Should -Be $script:testTextFile + $script:result.Search | Should -Be $script:testSearchNoFind + $script:result.Type | Should -Be 'Text' + $script:result.Text | Should -BeNullOrEmpty + $script:result.Encoding | Should -Be $script:testNonCompliantEncoding.Encoding + } + + It 'Should call the expected mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Assert-ParametersValid -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-Content ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 } } } @@ -181,6 +326,12 @@ Setting3.Test=Value4 -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + Mock ` -CommandName Set-Content ` -ParameterFilter { @@ -230,6 +381,12 @@ Setting3.Test=Value4 -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.encoding } ` + -Verifiable + Mock ` -CommandName Set-Content ` -ParameterFilter { @@ -279,6 +436,12 @@ Setting3.Test=Value4 -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + Mock ` -CommandName Set-Content ` -ParameterFilter { @@ -329,6 +492,12 @@ Setting3.Test=Value4 -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + Mock ` -CommandName Set-Content ` -ParameterFilter { @@ -379,6 +548,12 @@ Setting3.Test=Value4 -MockWith { $null } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.encoding } ` + -Verifiable + Mock ` -CommandName Set-Content ` -ParameterFilter { @@ -420,7 +595,7 @@ Setting3.Test=Value4 #region Function Test-TargetResource Describe 'DSR_ReplaceString\Test-TargetResource' { - Context 'File exists search text can not be found and AllowAppend is TRUE' { + Context 'File exists search text cannot be found and AllowAppend is TRUE' { # verifiable (should be called) mocks Mock ` -CommandName Assert-ParametersValid ` @@ -439,6 +614,12 @@ Setting3.Test=Value4 -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + $script:result = $null It 'Should not throw an exception' { @@ -463,10 +644,15 @@ Setting3.Test=Value4 -CommandName Get-Content ` -ParameterFilter { $path -eq $script:testTextFile } ` -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 } } - Context 'File exists search text can not be found and AllowAppend is FALSE' { + Context 'File exists search text cannot be found and AllowAppend is FALSE and encoding is in desired state' { # verifiable (should be called) mocks Mock ` -CommandName Assert-ParametersValid ` @@ -485,6 +671,12 @@ Setting3.Test=Value4 -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testCompliantEncoding.Encoding } ` + -Verifiable + $script:result = $null It 'Should not throw an exception' { @@ -493,11 +685,12 @@ Setting3.Test=Value4 -Search $script:testSearchNoFind ` -Text $script:testTextReplace ` -AllowAppend $false ` + -Encoding $script:fileEncodingParameters.Encoding ` -Verbose } | Should -Not -Throw } - It 'Should return false' { + It 'Should return true' { $script:result | Should -Be $true } @@ -509,9 +702,71 @@ Setting3.Test=Value4 -CommandName Get-Content ` -ParameterFilter { $path -eq $script:testTextFile } ` -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 } } + Context 'File exists search text cannot be found and AllowAppend is FALSE and encoding is not in desired state' { + # verifiable (should be called) mocks + Mock ` + -CommandName Assert-ParametersValid ` + -ModuleName 'DSR_ReplaceText' ` + -Verifiable + + Mock ` + -CommandName Test-Path ` + -ModuleName 'DSR_ReplaceText' ` + -MockWith { $true } ` + -Verifiable + + Mock ` + -CommandName Get-Content ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testFileContent } ` + -Verifiable + + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -MockWith { $script:testNonCompliantEncoding.Encoding } ` + -Verifiable + + $script:result = $null + + It 'Should not throw an exception' { + { $script:result = Test-TargetResource ` + -Path $script:testTextFile ` + -Search $script:testSearchNoFind ` + -Text $script:testTextReplace ` + -AllowAppend $false ` + -Encoding $script:fileEncodingParameters.Encoding ` + -Verbose + } | Should -Not -Throw + } + + It 'Should return false' { + $script:result | Should -Be $false + } + + It 'Should call the expected mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Assert-ParametersValid -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-Content ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 + + Assert-MockCalled ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Exactly 1 + } + } Context 'File exists and search text can be found but does not match replace string' { # verifiable (should be called) mocks @@ -532,6 +787,11 @@ Setting3.Test=Value4 -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Verifiable + $script:result = $null It 'Should not throw an exception' { @@ -577,6 +837,11 @@ Setting3.Test=Value4 -MockWith { $script:testFileExpectedTextContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Verifiable + $script:result = $null It 'Should not throw an exception' { @@ -622,6 +887,11 @@ Setting3.Test=Value4 -MockWith { $script:testFileContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Verifiable + $script:result = $null It 'Should not throw an exception' { @@ -668,6 +938,11 @@ Setting3.Test=Value4 -MockWith { $script:testFileExpectedSecretContent } ` -Verifiable + Mock ` + -CommandName Get-FileEncoding ` + -ParameterFilter { $path -eq $script:testTextFile } ` + -Verifiable + $script:result = $null It 'Should not throw an exception' { diff --git a/Tests/Unit/FileContentDsc.Common.tests.ps1 b/Tests/Unit/FileContentDsc.Common.tests.ps1 index c3d002f..5c02df5 100644 --- a/Tests/Unit/FileContentDsc.Common.tests.ps1 +++ b/Tests/Unit/FileContentDsc.Common.tests.ps1 @@ -24,37 +24,66 @@ try $LocalizedData } - Describe "$($script:ModuleName)\Get-TextEolCharacter" { + Describe "$($script:ModuleName)\Get-TextEolCharacter" { + $textNoNewLine = 'NoNewLine' + $textCRLFOnly = "CRLFOnly`r`n" + $textCROnly = "CROnly`r" + $textBoth = "CRLFLine`r`nCRLine`r" - $textNoNewLine = 'NoNewLine' - $textCRLFOnly = "CRLFOnly`r`n" - $textCROnly = "CROnly`r" - $textBoth = "CRLFLine`r`nCRLine`r" + Context 'text with no new line' { + It 'should return CRLF' { + Get-TextEolCharacter -Text $textNoNewLine | Should -Be "`r`n" + } + } - Context 'text with no new line' { - It 'should return CRLF' { - Get-TextEolCharacter -Text $textNoNewLine | Should -Be "`r`n" - } + Context 'text with CRLF only' { + It 'should return CRLF' { + Get-TextEolCharacter -Text $textCRLFOnly | Should -Be "`r`n" } + } - Context 'text with CRLF only' { - It 'should return CRLF' { - Get-TextEolCharacter -Text $textCRLFOnly | Should -Be "`r`n" - } + Context 'text with CR only' { + It 'should return CR' { + Get-TextEolCharacter -Text $textCROnly | Should -Be "`r" } + } - Context 'text with CR only' { - It 'should return CR' { - Get-TextEolCharacter -Text $textCROnly | Should -Be "`r" - } + Context 'text with both CR and CRLF' { + It 'should return CRLF' { + Get-TextEolCharacter -Text $textBoth | Should -Be "`r`n" } + } + } + + Describe "$($script:ModuleName)\Get-FileEncoding" { + $testTextFile = "TestDrive:\TestFile.txt" + $value = 'testText' + $encoding = @( + @{ + encoding = 'ASCII' + }, + @{ + encoding = 'BigEndianUnicode' + }, + @{ + encoding = 'BigEndianUTF32' + }, + @{ + encoding = 'UTF8' + }, + @{ + encoding = 'UTF32' + } + ) - Context 'text with both CR and CRLF' { - It 'should return CRLF' { - Get-TextEolCharacter -Text $textBoth | Should -Be "`r`n" - } + Context 'When checking file encoding' { + It "encoding is and should return " -TestCases $encoding { + param($encoding) + Set-Content $testTextFile -Value $value -Encoding $encoding + Get-FileEncoding -Path $testTextFile | Should -Be $encoding } } + } #endregion } finally