├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── General.md │ ├── Problem_with_resource.yml │ ├── Resource_proposal.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md └── stale.yml ├── .gitignore ├── .markdownlint.json ├── .vscode ├── analyzersettings.psd1 └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── GitVersion.yml ├── LICENSE ├── README.md ├── RequiredModules.psd1 ├── Resolve-Dependency.ps1 ├── Resolve-Dependency.psd1 ├── SECURITY.md ├── azure-pipelines.yml ├── build.ps1 ├── build.yaml ├── codecov.yml ├── source ├── Build.psd1 ├── DSCResources │ ├── DSC_IniSettingsFile │ │ ├── DSC_IniSettingsFile.psm1 │ │ ├── DSC_IniSettingsFile.schema.mof │ │ ├── README.md │ │ └── en-US │ │ │ ├── DSC_IniSettingsFile.schema.mfl │ │ │ └── DSC_IniSettingsFile.strings.psd1 │ ├── DSC_KeyValuePairFile │ │ ├── DSC_KeyValuePairFile.psm1 │ │ ├── DSC_KeyValuePairFile.schema.mof │ │ ├── README.md │ │ └── en-US │ │ │ ├── DSC_KeyValuePairFile.schema.mfl │ │ │ └── DSC_KeyValuePairFile.strings.psd1 │ └── DSC_ReplaceText │ │ ├── DSC_ReplaceText.psm1 │ │ ├── DSC_ReplaceText.schema.mof │ │ ├── README.md │ │ └── en-US │ │ ├── DSC_ReplaceText.schema.mfl │ │ └── DSC_ReplaceText.strings.psd1 ├── Examples │ ├── README.md │ └── Resources │ │ ├── IniSettingsFile │ │ ├── 1-IniSettingsFile_SetPlainTextEntry_Config.ps1 │ │ └── 2-IniSettingsFile_SetSecretTextEntry_Config.ps1 │ │ ├── KeyValuePairFile │ │ ├── 1-KeyValuePairFile_RemovePlainTextPair_Config.ps1 │ │ ├── 2-KeyValuePairFile_SetPlainTextPair_Config.ps1 │ │ └── 3-KeyValuePairFile_SetSecretTextPair_Config.ps1 │ │ └── ReplaceText │ │ ├── 1-ReplaceText_ReplacePlainSecretText_Config.ps1 │ │ ├── 2-ReplaceText_ReplacePlainText_Config.ps1 │ │ └── 3-ReplaceText_ReplaceRegexText_Config.ps1 ├── FileContentDsc.psd1 ├── Modules │ └── FileContentDsc.Common │ │ ├── FileContentDsc.Common.psm1 │ │ └── en-US │ │ └── FileContentDsc.Common.strings.psd1 ├── WikiSource │ └── Home.md └── en-US │ └── about_FileContentDsc.help.txt └── tests ├── Integration ├── DSC_IniSettingsFile.Integration.Tests.ps1 ├── DSC_IniSettingsFile.config.ps1 ├── DSC_KeyValuePairFile.Integration.Tests.ps1 ├── DSC_KeyValuePairFile.config.ps1 ├── DSC_ReplaceText.Integration.Tests.ps1 └── DSC_ReplaceText.config.ps1 ├── TestHelpers └── CommonTestHelper.psm1 └── Unit ├── DSC_IniSettingsFile.Tests.ps1 ├── DSC_KeyValuePairFile.Tests.ps1 ├── DSC_ReplaceText.Tests.ps1 └── FileContentDsc.Common.tests.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text eol=crlf 5 | 6 | # Ensure any exe files are treated as binary 7 | *.exe binary 8 | *.jpg binary 9 | *.xl* binary 10 | *.pfx binary 11 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please check out common DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing). 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/General.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General question or documentation update 3 | about: If you have a general question or documentation update suggestion around the resource module. 4 | --- 5 | 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Problem_with_resource.yml: -------------------------------------------------------------------------------- 1 | name: Problem with a resource 2 | description: If you have a problem, bug, or enhancement with a resource in this resource module. 3 | labels: [] 4 | assignees: [] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please prefix the issue title (above) with the resource name, e.g. 'ResourceName: Short description of my issue'! 10 | 11 | Your feedback and support is greatly appreciated, thanks for contributing! 12 | - type: textarea 13 | id: description 14 | attributes: 15 | label: Problem description 16 | description: Details of the scenario you tried and the problem that is occurring. 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: logs 21 | attributes: 22 | label: Verbose logs 23 | description: | 24 | Verbose logs showing the problem. **NOTE! Sensitive information should be obfuscated.** _Will be automatically formatted as plain text._ 25 | placeholder: | 26 | Paste verbose logs here 27 | render: text 28 | validations: 29 | required: true 30 | - type: textarea 31 | id: configuration 32 | attributes: 33 | label: DSC configuration 34 | description: | 35 | The DSC configuration that is used to reproduce the issue (as detailed as possible). **NOTE! Sensitive information should be obfuscated.** _Will be automatically formatted as PowerShell code._ 36 | placeholder: | 37 | Paste DSC configuration here 38 | render: powershell 39 | validations: 40 | required: true 41 | - type: textarea 42 | id: suggestedSolution 43 | attributes: 44 | label: Suggested solution 45 | description: Do you have any suggestions how to solve the issue? 46 | validations: 47 | required: true 48 | - type: textarea 49 | id: targetNodeOS 50 | attributes: 51 | label: Operating system the target node is running 52 | description: | 53 | Please provide as much as possible about the target node, for example edition, version, build, and language. _Will be automatically formatted as plain text._ 54 | 55 | On OS with WMF 5.1 the following command can help get this information: `Get-ComputerInfo -Property @('OsName','OsOperatingSystemSKU','OSArchitecture','WindowsVersion','WindowsBuildLabEx','OsLanguage','OsMuiLanguages')` 56 | placeholder: | 57 | Add operating system information here 58 | render: text 59 | validations: 60 | required: true 61 | - type: textarea 62 | id: targetNodePS 63 | attributes: 64 | label: PowerShell version and build the target node is running 65 | description: | 66 | Please provide the version and build of PowerShell the target node is running. _Will be automatically formatted as plain text._ 67 | 68 | To help with this information, please run this command: `$PSVersionTable` 69 | placeholder: | 70 | Add PowerShell information here 71 | render: text 72 | validations: 73 | required: true 74 | - type: textarea 75 | id: moduleVersion 76 | attributes: 77 | label: FileContentDsc version 78 | description: | 79 | Please provide the version of the FileContentDsc module that was used. _Will be automatically formatted as plain text._ 80 | 81 | To help with this information, please run this command: `Get-Module -Name 'FileContentDsc' -ListAvailable | ft Name,Version,Path` 82 | placeholder: | 83 | Add module information here 84 | render: text 85 | validations: 86 | required: true 87 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Resource_proposal.yml: -------------------------------------------------------------------------------- 1 | name: New resource proposal 2 | description: If you have a new resource proposal that you think should be added to this resource module. 3 | title: "NewResourceName: New resource proposal" 4 | labels: [] 5 | assignees: [] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Please replace `NewResourceName` in the issue title (above) with your proposed resource name. 11 | 12 | Thank you for contributing and making this resource module better! 13 | - type: textarea 14 | id: description 15 | attributes: 16 | label: Resource proposal 17 | description: Provide information how this resource will/should work and how it will help users. 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: proposedProperties 22 | attributes: 23 | label: Proposed properties 24 | description: | 25 | List all the proposed properties that the resource should have (key, required, write, and/or read). For each property provide a detailed description, the data type, if a default value should be used, and if the property is limited to a set of values. 26 | value: | 27 | Property | Type qualifier | Data type | Description | Default value | Allowed values 28 | --- | --- | --- | --- | --- | --- 29 | PropertyName | Key | String | Detailed description | None | None 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: considerations 34 | attributes: 35 | label: Special considerations or limitations 36 | description: | 37 | Provide any considerations or limitations you can think of that a contributor should take in account when coding the proposed resource, and or what limitations a user will encounter or should consider when using the proposed resource. 38 | validations: 39 | required: true 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: "Virtual PowerShell User Group #DSC channel" 4 | url: https://dsccommunity.org/community/contact/ 5 | about: "To talk to the community and maintainers of DSC Community, please visit the #DSC channel." 6 | 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 14 | 15 | #### Pull Request (PR) description 16 | 17 | 23 | 24 | #### This Pull Request (PR) fixes the following issues 25 | 26 | 34 | 35 | #### Task list 36 | 37 | 45 | 46 | - [ ] Added an entry to the change log under the Unreleased section of the 47 | file CHANGELOG.md. Entry should say what was changed and how that 48 | affects users (if applicable), and reference the issue being resolved 49 | (if applicable). 50 | - [ ] Resource documentation added/updated in README.md. 51 | - [ ] Resource parameter descriptions added/updated in README.md, schema.mof 52 | and comment-based help. 53 | - [ ] Comment-based help added/updated. 54 | - [ ] Localization strings added/updated in all localization files as appropriate. 55 | - [ ] Examples appropriately added/updated. 56 | - [ ] Unit tests added/updated. See [DSC Community Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines). 57 | - [ ] Integration tests added/updated (where possible). See [DSC Community Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines). 58 | - [ ] New/changed code adheres to [DSC Community Style Guidelines](https://dsccommunity.org/styleguidelines). 59 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | limitPerRun: 30 4 | 5 | pulls: 6 | daysUntilStale: 14 7 | daysUntilClose: false 8 | exemptProjects: true 9 | exemptMilestones: true 10 | staleLabel: abandoned 11 | exemptLabels: 12 | - needs review 13 | - on hold 14 | - waiting for CLA pass 15 | 16 | markComment: > 17 | Labeling this pull request (PR) as abandoned since it has gone 14 days or more 18 | since the last update. An abandoned PR can be continued by another contributor. 19 | The abandoned label will be removed if work on this PR is taken up again. 20 | 21 | issues: 22 | daysUntilStale: 30 23 | daysUntilClose: 40 24 | exemptProjects: true 25 | exemptMilestones: true 26 | staleLabel: stale 27 | exemptLabels: 28 | - bug 29 | - enhancement 30 | - tests 31 | - documentation 32 | - resource proposal 33 | - on hold 34 | 35 | markComment: > 36 | This issue has been automatically marked as stale because 37 | it has not had activity from the community in the last 30 days. It will be 38 | closed if no further activity occurs within 10 days. If the issue is labelled 39 | with any of the work labels (e.g bug, enhancement, documentation, or tests) 40 | then the issue will not auto-close. 41 | 42 | closeComment: > 43 | This issue has been automatically closed because it is has not had activity 44 | from the community in the last 40 days. 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.user 3 | *.coverage 4 | .vs 5 | .vscode 6 | .psproj 7 | .sln 8 | markdownissues.txt 9 | TestResults.xml 10 | output/ 11 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD029": { 4 | "style": "one" 5 | }, 6 | "MD013": true, 7 | "MD024": true, 8 | "MD034": true, 9 | "no-hard-tabs": true 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/analyzersettings.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | CustomRulePath = @( 3 | './output/RequiredModules/DscResource.AnalyzerRules' 4 | './output/RequiredModules/Indented.ScriptAnalyzerRules' 5 | ) 6 | IncludeDefaultRules = $true 7 | IncludeRules = @( 8 | # DSC Community style guideline rules from the module ScriptAnalyzer. 9 | 'PSAvoidDefaultValueForMandatoryParameter' 10 | 'PSAvoidDefaultValueSwitchParameter' 11 | 'PSAvoidInvokingEmptyMembers' 12 | 'PSAvoidNullOrEmptyHelpMessageAttribute' 13 | 'PSAvoidUsingCmdletAliases' 14 | 'PSAvoidUsingComputerNameHardcoded' 15 | 'PSAvoidUsingDeprecatedManifestFields' 16 | 'PSAvoidUsingEmptyCatchBlock' 17 | 'PSAvoidUsingInvokeExpression' 18 | 'PSAvoidUsingPositionalParameters' 19 | 'PSAvoidShouldContinueWithoutForce' 20 | 'PSAvoidUsingWMICmdlet' 21 | 'PSAvoidUsingWriteHost' 22 | 'PSDSCReturnCorrectTypesForDSCFunctions' 23 | 'PSDSCStandardDSCFunctionsInResource' 24 | 'PSDSCUseIdenticalMandatoryParametersForDSC' 25 | 'PSDSCUseIdenticalParametersForDSC' 26 | 'PSMisleadingBacktick' 27 | 'PSMissingModuleManifestField' 28 | 'PSPossibleIncorrectComparisonWithNull' 29 | 'PSProvideCommentHelp' 30 | 'PSReservedCmdletChar' 31 | 'PSReservedParams' 32 | 'PSUseApprovedVerbs' 33 | 'PSUseCmdletCorrectly' 34 | 'PSUseOutputTypeCorrectly' 35 | 'PSAvoidGlobalVars' 36 | 'PSAvoidUsingConvertToSecureStringWithPlainText' 37 | 'PSAvoidUsingPlainTextForPassword' 38 | 'PSAvoidUsingUsernameAndPasswordParams' 39 | 'PSDSCUseVerboseMessageInDSCResource' 40 | 'PSShouldProcess' 41 | 'PSUseDeclaredVarsMoreThanAssignments' 42 | 'PSUsePSCredentialType' 43 | 44 | # Additional rules from the module ScriptAnalyzer 45 | 'PSUseConsistentWhitespace' 46 | 'UseCorrectCasing' 47 | 'PSPlaceOpenBrace' 48 | 'PSPlaceCloseBrace' 49 | 'AlignAssignmentStatement' 50 | 'AvoidUsingDoubleQuotesForConstantString' 51 | 'UseShouldProcessForStateChangingFunctions' 52 | 53 | # Rules from the modules DscResource.AnalyzerRules 54 | 'Measure-*' 55 | 56 | # Rules from the module Indented.ScriptAnalyzerRules 57 | 'AvoidCreatingObjectsFromAnEmptyString' 58 | 'AvoidDashCharacters' 59 | 'AvoidEmptyNamedBlocks' 60 | 'AvoidFilter' 61 | 'AvoidHelpMessage' 62 | 'AvoidNestedFunctions' 63 | 'AvoidNewObjectToCreatePSObject' 64 | 'AvoidParameterAttributeDefaultValues' 65 | 'AvoidProcessWithoutPipeline' 66 | 'AvoidSmartQuotes' 67 | 'AvoidThrowOutsideOfTry' 68 | 'AvoidWriteErrorStop' 69 | 'AvoidWriteOutput' 70 | 'UseSyntacticallyCorrectExamples' 71 | ) 72 | 73 | <# 74 | The following types are not rules but parse errors reported by PSScriptAnalyzer 75 | so they cannot be ecluded. They need to be filtered out from the result of 76 | Invoke-ScriptAnalyzer. 77 | 78 | TypeNotFound - Because classes in the project cannot be found unless built. 79 | RequiresModuleInvalid - Because 'using module' in prefix.ps1 cannot be resolved as source file. 80 | #> 81 | ExcludeRules = @() 82 | 83 | Rules = @{ 84 | PSUseConsistentWhitespace = @{ 85 | Enable = $true 86 | CheckOpenBrace = $true 87 | CheckInnerBrace = $true 88 | CheckOpenParen = $true 89 | CheckOperator = $false 90 | CheckSeparator = $true 91 | CheckPipe = $true 92 | CheckPipeForRedundantWhitespace = $true 93 | CheckParameter = $false 94 | } 95 | 96 | PSPlaceOpenBrace = @{ 97 | Enable = $true 98 | OnSameLine = $false 99 | NewLineAfter = $true 100 | IgnoreOneLineBlock = $false 101 | } 102 | 103 | PSPlaceCloseBrace = @{ 104 | Enable = $true 105 | NoEmptyLineBefore = $true 106 | IgnoreOneLineBlock = $false 107 | NewLineAfter = $true 108 | } 109 | 110 | PSAlignAssignmentStatement = @{ 111 | Enable = $true 112 | CheckHashtable = $true 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "powershell.codeFormatting.openBraceOnSameLine": false, 3 | "powershell.codeFormatting.newLineAfterOpenBrace": true, 4 | "powershell.codeFormatting.newLineAfterCloseBrace": true, 5 | "powershell.codeFormatting.whitespaceBeforeOpenBrace": true, 6 | "powershell.codeFormatting.whitespaceBeforeOpenParen": true, 7 | "powershell.codeFormatting.whitespaceAroundOperator": true, 8 | "powershell.codeFormatting.whitespaceAfterSeparator": true, 9 | "powershell.codeFormatting.ignoreOneLineBlock": false, 10 | "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationForFirstPipeline", 11 | "powershell.codeFormatting.preset": "Custom", 12 | "powershell.codeFormatting.alignPropertyValuePairs": true, 13 | "powershell.codeFormatting.useConstantStrings": true, 14 | "powershell.developer.bundledModulesPath": "${cwd}/output/RequiredModules", 15 | "powershell.scriptAnalysis.settingsPath": "/.vscode/analyzersettings.psd1", 16 | "powershell.scriptAnalysis.enable": true, 17 | "files.trimTrailingWhitespace": true, 18 | "files.trimFinalNewlines": true, 19 | "files.insertFinalNewline": true, 20 | "files.associations": { 21 | "*.ps1xml": "xml" 22 | }, 23 | "cSpell.dictionaries": [ 24 | "powershell" 25 | ], 26 | "cSpell.words": [ 27 | "COMPANYNAME", 28 | "ICONURI", 29 | "LICENSEURI", 30 | "PROJECTURI", 31 | "RELEASENOTES", 32 | "buildhelpers", 33 | "endregion", 34 | "gitversion", 35 | "icontains", 36 | "keepachangelog", 37 | "notin", 38 | "pscmdlet", 39 | "steppable" 40 | ], 41 | "cSpell.ignorePaths": [ 42 | ".git" 43 | ], 44 | "[markdown]": { 45 | "files.trimTrailingWhitespace": true, 46 | "files.encoding": "utf8" 47 | }, 48 | "powershell.pester.useLegacyCodeLens": false, 49 | "pester.testFilePath": [ 50 | "[tT]ests/[qQ][aA]/*.[tT]ests.[pP][sS]1", 51 | "[tT]ests/[uU]nit/**/*.[tT]ests.[pP][sS]1", 52 | "[tT]ests/[uU]nit/*.[tT]ests.[pP][sS]1" 53 | ], 54 | "pester.runTestsInNewProcess": true, 55 | "pester.pesterModulePath": "./output/RequiredModules/Pester", 56 | "powershell.pester.codeLens": true, 57 | "pester.suppressCodeLensNotice": true, 58 | } 59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log for FileContentDsc 2 | 3 | The format is based on and uses the types of changes according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 4 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 5 | 6 | ## [Unreleased] 7 | 8 | ### Changed 9 | 10 | - Transferred ownership to DSCCommunity.org - 11 | fixes [Issue #31](https://github.com/dsccommunity/FileContentDsc/issues/39). 12 | - BREAKING CHANGE: Changed resource prefix from MSFT to DSC. 13 | - Updated to use continuous delivery pattern using Azure DevOps - fixes 14 | [Issue #41](https://github.com/dsccommunity/FileContentDsc/issues/41). 15 | - Updated build badges in README.MD. 16 | - Change Azure DevOps Pipeline definition to include `source/*` - Fixes [Issue #45](https://github.com/dsccommunity/FileContentDsc/issues/45). 17 | - Updated pipeline to use `latest` version of `ModuleBuilder` - Fixes [Issue #45](https://github.com/dsccommunity/FileContentDsc/issues/45). 18 | - Merge `HISTORIC_CHANGELOG.md` into `CHANGELOG.md` - Fixes [Issue #46](https://github.com/dsccommunity/FileContentDsc/issues/46). 19 | - Fixed build failures caused by changes in `ModuleBuilder` module v1.7.0 20 | by changing `CopyDirectories` to `CopyPaths` - Fixes [Issue #49](https://github.com/dsccommunity/FileContentDsc/issues/49). 21 | - Updated to use the common module _DscResource.Common_ - Fixes [Issue #48](https://github.com/dsccommunity/FileContentDsc/issues/48). 22 | - Pin `Pester` module to 4.10.1 because Pester 5.0 is missing code 23 | coverage - Fixes [Issue #50](https://github.com/dsccommunity/FileContentDsc/issues/50). 24 | - Automatically publish documentation to GitHub Wiki - Fixes [Issue #51](https://github.com/dsccommunity/FileContentDsc/issues/51). 25 | - Renamed `master` branch to `main` - Fixes [Issue #53](https://github.com/dsccommunity/FileContentDsc/issues/53). 26 | - Updated `GitVersion.yml` to latest pattern - Fixes [Issue #57](https://github.com/dsccommunity/FileContentDsc/issues/57). 27 | - Updated build to use `Sampler.GitHubTasks` - Fixes [Issue #60](https://github.com/dsccommunity/FileContentDsc/issues/60). 28 | - Added support for publishing code coverage to `CodeCov.io` and 29 | Azure Pipelines - Fixes [Issue #61](https://github.com/dsccommunity/FileContentDsc/issues/61). 30 | - Updated .github issue templates to standard - Fixes [Issue #64](https://github.com/dsccommunity/FileContentDsc/issues/64). 31 | - Added Create_ChangeLog_GitHub_PR task to publish stage of build pipeline. 32 | - Added SECURITY.md. 33 | - Updated pipeline Deploy_Module anb Code_Coverage jobs to use ubuntu-latest 34 | images - Fixes [Issue #63](https://github.com/dsccommunity/FileContentDsc/issues/63). 35 | - Updated pipeline unit tests and integration tests to use Windows Server 2019 and 36 | Windows Server 2022 images - Fixes [Issue #63](https://github.com/dsccommunity/FileContentDsc/issues/63). 37 | - CI Pipeline 38 | - Updated pipeline files to match current DSC Community patterns - fixes [Issue #71](https://github.com/dsccommunity/FileContentDsc/issues/71). 39 | - Updated HQRM and build steps to use windows-latest image. 40 | - Removed `windows-2019` before deprecation fixes [#76](https://github.com/dsccommunity/FileContentDsc/issues/76). 41 | - Added `windows-2025` images. 42 | 43 | ### Fixed 44 | 45 | - Fixed pipeline by replacing the GitVersion task in the `azure-pipelines.yml` 46 | with a script. 47 | 48 | ## [1.3.0.151] - 2019-07-20 49 | 50 | ### Changed 51 | 52 | - Opted into Common Tests 'Common Tests - Validate Localization' - 53 | fixes [Issue #31](https://github.com/PlagueHO/FileContentDsc/issues/32). 54 | - Combined all `FileContent.ResourceHelper` module functions into 55 | `FileContent.Common` module - fixes [Issue #32](https://github.com/PlagueHO/FileContentDsc/issues/32). 56 | - Renamed all localization strings so that they are detected by 57 | 'Common Tests - Validate Localization'. 58 | - Correct style violations in unit tests: 59 | - Adding `Get`, `Set` and `Test` tags to appropriate `describe` blocks. 60 | - Removing uneccesary `#region` blocks. 61 | - Conversion of double quotes to single quotes where possible. 62 | - Replace variables with string litterals in `describe` block description. 63 | - KeyValuePairFile: 64 | - Improve unit tests to simplify and cover additional test cases. 65 | - Fix error occuring when file is empty or does not exist - fixes [Issue #34](https://github.com/PlagueHO/FileContentDsc/issues/34). 66 | 67 | ## [1.2.0.138] - 2018-10-27 68 | 69 | ### Changed 70 | 71 | - Added .VSCode settings for applying DSC PSSA rules - fixes [Issue #25](https://github.com/PlagueHO/FileContentDsc/issues/25). 72 | - Added an Encoding parameter to the KeyValuePairFile and ReplaceText 73 | resources - fixes [Issue #5](https://github.com/PlagueHO/FileContentDsc/issues/5). 74 | 75 | ## [1.1.0.108] - 2018-10-02 76 | 77 | ### Changed 78 | 79 | - Enabled PSSA rule violations to fail build - Fixes [Issue #6](https://github.com/PlagueHO/FileContentDsc/issues/6). 80 | - Updated tests to meet Pester v4 standard. 81 | - Added Open Code of Conduct. 82 | - Refactored module folder structure to move resource 83 | to root folder of repository and remove test harness - Fixes [Issue #11](https://github.com/PlagueHO/FileContentDsc/issues/11). 84 | - Converted Examples to support format for publishing to PowerShell 85 | Gallery. 86 | - Refactor Test-TargetResource to return $false in all DSC resource - Fixes 87 | [Issue #12](https://github.com/PlagueHO/FileContentDsc/issues/13). 88 | - Correct configuration names in Examples - fixes [Issue #15](https://github.com/PowerShell/FileContentDsc/issues/15). 89 | - Refactor Test/Set-TargetResource in ReplaceText to be able to add a key if it 90 | doesn't exist but should -Fixes 91 | [Issue#20](https://github.com/PlagueHO/FileContentDsc/issues/20). 92 | - Opted into common tests: 93 | - Common Tests - Validate Example Files To Be Published 94 | - Common Tests - Validate Markdown Links 95 | - Common Tests - Relative Path Length 96 | - Common Tests - Relative Path Length 97 | - Correct test context description in IniSettingsFile tests to include 'When'. 98 | - Change IniSettingsFile unit tests to be non-destructive - fixes [Issue #22](https://github.com/PowerShell/FileContentDsc/issues/22). 99 | - Update to new format LICENSE. 100 | 101 | ## [1.0.0.38] - 2017-09-02 102 | 103 | ### Changed 104 | 105 | - DSR_ReplaceText: 106 | - Created new resource for replacing text in text files. 107 | - DSR_KeyValuePairFile: 108 | - Created new resource for setting key value pairs in text files. 109 | - DSR_IniSettingsFile: 110 | - Created new resource for setting Windows INI file settings. 111 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This project has adopted the [DSC Community Code of Conduct](https://dsccommunity.org/code_of_conduct). 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing 3 | 4 | Please check out common DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing). 5 | 6 | ## Running the Tests 7 | 8 | If want to know how to run this module's tests you can look at the [Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines/#running-tests) 9 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: ContinuousDelivery 2 | next-version: 1.3.0 3 | major-version-bump-message: '(breaking\schange|breaking)\b' 4 | minor-version-bump-message: '(adds?|minor)\b' 5 | patch-version-bump-message: '\s?(fix|patch)' 6 | no-bump-message: '\+semver:\s?(none|skip)' 7 | assembly-informational-format: '{NuGetVersionV2}+Sha.{Sha}.Date.{CommitDate}' 8 | branches: 9 | master: 10 | tag: preview 11 | regex: ^main$ 12 | pull-request: 13 | tag: PR 14 | feature: 15 | tag: useBranchName 16 | increment: Minor 17 | regex: f(eature(s)?)?[\/-] 18 | source-branches: ['master'] 19 | hotfix: 20 | tag: fix 21 | increment: Patch 22 | regex: (hot)?fix(es)?[\/-] 23 | source-branches: ['master'] 24 | ignore: 25 | sha: [] 26 | merge-message-formats: {} 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright the DSC Community contributors. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FileContentDsc 2 | 3 | [![Build Status](https://dev.azure.com/dsccommunity/FileContentDsc/_apis/build/status/dsccommunity.FileContentDsc?branchName=main)](https://dev.azure.com/dsccommunity/FileContentDsc/_build/latest?definitionId=31&branchName=main) 4 | ![Code Coverage](https://img.shields.io/azure-devops/coverage/dsccommunity/FileContentDsc/31/main) 5 | [![Azure DevOps tests](https://img.shields.io/azure-devops/tests/dsccommunity/FileContentDsc/31/main)](https://dsccommunity.visualstudio.com/FileContentDsc/_test/analytics?definitionId=31&contextType=build) 6 | [![PowerShell Gallery (with prereleases)](https://img.shields.io/powershellgallery/vpre/FileContentDsc?label=FileContentDsc%20Preview)](https://www.powershellgallery.com/packages/FileContentDsc/) 7 | [![PowerShell Gallery](https://img.shields.io/powershellgallery/v/FileContentDsc?label=FileContentDsc)](https://www.powershellgallery.com/packages/FileContentDsc/) 8 | [![codecov](https://codecov.io/gh/dsccommunity/FileContentDsc/branch/main/graph/badge.svg)](https://codecov.io/gh/dsccommunity/FileContentDsc) 9 | 10 | ## Code of Conduct 11 | 12 | This project has adopted [this code of conduct](CODE_OF_CONDUCT.md). 13 | 14 | ## Releases 15 | 16 | For each merge to the branch `main` a preview release will be 17 | deployed to [PowerShell Gallery](https://www.powershellgallery.com/). 18 | Periodically a release version tag will be pushed which will deploy a 19 | full release to [PowerShell Gallery](https://www.powershellgallery.com/). 20 | 21 | ## Contributing 22 | 23 | Please check out common DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing). 24 | 25 | ## Change log 26 | 27 | A full list of changes in each version can be found in the [change log](CHANGELOG.md). 28 | 29 | ## Resources 30 | 31 | This resource module contains resources for setting the content of files. 32 | Configuration text files are the most common use case for this module. 33 | 34 | The **FileContent** module contains the following resources: 35 | 36 | - **IniSettingsFile**: Add, set or clear entries in Windows INI settings files. 37 | - **KeyValuePairFile**: Add, remove or set key/value pairs in a text file containing 38 | key/value pairs, and set file encoding. 39 | - **ReplaceText**: Replaces strings matching a regular expression in a file, 40 | and sets file encoding. 41 | 42 | ## Documentation and Examples 43 | 44 | For a full list of resources in FileContentDsc and examples on their use, check out 45 | the [FileContentDsc wiki](https://github.com/dsccommunity/FileContentDsc/wiki). 46 | -------------------------------------------------------------------------------- /RequiredModules.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | PSDependOptions = @{ 3 | AddToPath = $true 4 | Target = 'output\RequiredModules' 5 | Parameters = @{ 6 | Repository = '' 7 | } 8 | } 9 | 10 | InvokeBuild = 'latest' 11 | PSScriptAnalyzer = 'latest' 12 | Pester = '4.10.1' 13 | Plaster = 'latest' 14 | ModuleBuilder = 'latest' 15 | ChangelogManagement = 'latest' 16 | Sampler = 'latest' 17 | 'Sampler.GitHubTasks' = 'latest' 18 | MarkdownLinkCheck = 'latest' 19 | 'DscResource.Test' = 'latest' 20 | xDscResourceDesigner = 'latest' 21 | 22 | # Build dependencies needed for using the module 23 | 'DscResource.Common' = 'latest' 24 | 25 | # Analyzer rules 26 | 'DscResource.AnalyzerRules' = 'latest' 27 | 'Indented.ScriptAnalyzerRules' = 'latest' 28 | 29 | # Prerequisite modules for documentation. 30 | 'DscResource.DocGenerator' = 'latest' 31 | PlatyPS = 'latest' 32 | } 33 | -------------------------------------------------------------------------------- /Resolve-Dependency.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | Gallery = 'PSGallery' 3 | AllowPrerelease = $false 4 | WithYAML = $true 5 | 6 | #UseModuleFast = $true 7 | #ModuleFastVersion = '0.1.2' 8 | #ModuleFastBleedingEdge = $true 9 | 10 | UsePSResourceGet = $true 11 | #PSResourceGetVersion = '1.0.1' 12 | 13 | UsePowerShellGetCompatibilityModule = $true 14 | UsePowerShellGetCompatibilityModuleVersion = '3.0.23-beta23' 15 | } 16 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | The DSC Community takes the security of our modules seriously, which includes all source code repositories managed through our GitHub organization. 4 | 5 | If you believe you have found a security vulnerability in any DSC Community owned repository, please report it to us as described below. 6 | 7 | ## Reporting Security Issues 8 | 9 | **Please do not report security vulnerabilities through public GitHub issues.** 10 | 11 | Instead, please report them to one or several members of the DSC Community organization. 12 | The easiest way to do so is to send us a direct message via twitter or slack. 13 | 14 | You should receive a response within 48 hours. If for some reason you do not, please follow up to other member of the community. 15 | 16 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 17 | 18 | * Type of issue 19 | * Full paths of source file(s) related to the manifestation of the issue 20 | * The location of the affected source code (tag/branch/commit or direct URL) 21 | * Any special configuration required to reproduce the issue 22 | * Step-by-step instructions to reproduce the issue 23 | * Proof-of-concept or exploit code (if possible) 24 | * Impact of the issue, including how an attacker might exploit the issue 25 | 26 | This information will help us triage your report more quickly. 27 | 28 | ## Preferred Languages 29 | 30 | We prefer all communications to be in English. 31 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - main 5 | paths: 6 | include: 7 | - source/* 8 | tags: 9 | include: 10 | - "v*" 11 | exclude: 12 | - "*-*" 13 | 14 | variables: 15 | buildFolderName: output 16 | buildArtifactName: output 17 | testResultFolderName: testResults 18 | testArtifactName: testResults 19 | sourceFolderName: source 20 | 21 | stages: 22 | - stage: Build 23 | jobs: 24 | - job: Package_Module 25 | displayName: 'Package Module' 26 | pool: 27 | vmImage: 'windows-latest' 28 | steps: 29 | - pwsh: | 30 | dotnet tool install --global GitVersion.Tool --version 5.* 31 | $gitVersionObject = dotnet-gitversion | ConvertFrom-Json 32 | $gitVersionObject.PSObject.Properties.ForEach{ 33 | Write-Host -Object "Setting Task Variable '$($_.Name)' with value '$($_.Value)'." 34 | Write-Host -Object "##vso[task.setvariable variable=$($_.Name);]$($_.Value)" 35 | } 36 | Write-Host -Object "##vso[build.updatebuildnumber]$($gitVersionObject.FullSemVer)" 37 | displayName: Calculate ModuleVersion (GitVersion) 38 | 39 | - task: PowerShell@2 40 | name: package 41 | displayName: 'Build & Package Module' 42 | inputs: 43 | filePath: './build.ps1' 44 | arguments: '-ResolveDependency -tasks pack' 45 | pwsh: true 46 | env: 47 | ModuleVersion: $(NuGetVersionV2) 48 | 49 | - task: PublishPipelineArtifact@1 50 | displayName: 'Publish Pipeline Artifact' 51 | inputs: 52 | targetPath: '$(buildFolderName)/' 53 | artifact: $(buildArtifactName) 54 | publishLocation: 'pipeline' 55 | parallel: true 56 | 57 | - stage: Test 58 | dependsOn: Build 59 | jobs: 60 | - job: Test_HQRM 61 | displayName: 'HQRM' 62 | pool: 63 | vmImage: 'windows-latest' 64 | timeoutInMinutes: '0' 65 | steps: 66 | - task: DownloadPipelineArtifact@2 67 | displayName: 'Download Pipeline Artifact' 68 | inputs: 69 | buildType: 'current' 70 | artifactName: $(buildArtifactName) 71 | targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' 72 | 73 | - task: PowerShell@2 74 | name: test 75 | displayName: 'Run HQRM Test' 76 | inputs: 77 | filePath: './build.ps1' 78 | arguments: '-Tasks hqrmtest' 79 | pwsh: false 80 | 81 | - task: PublishTestResults@2 82 | displayName: 'Publish Test Results' 83 | inputs: 84 | testResultsFormat: 'NUnit' 85 | testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' 86 | testRunTitle: 'HQRM' 87 | condition: succeededOrFailed() 88 | 89 | - job: Test_Unit_2022 90 | displayName: 'Unit (Windows Server 2022)' 91 | pool: 92 | vmImage: 'windows-2022' 93 | timeoutInMinutes: '0' 94 | steps: 95 | - task: DownloadPipelineArtifact@2 96 | displayName: 'Download Pipeline Artifact' 97 | inputs: 98 | buildType: 'current' 99 | artifactName: $(buildArtifactName) 100 | targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' 101 | 102 | - task: PowerShell@2 103 | name: test 104 | displayName: 'Run Unit Test' 105 | inputs: 106 | filePath: './build.ps1' 107 | arguments: "-Tasks test -PesterScript 'tests/Unit'" 108 | pwsh: false 109 | 110 | - task: PublishTestResults@2 111 | displayName: 'Publish Test Results' 112 | inputs: 113 | testResultsFormat: 'NUnit' 114 | testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' 115 | testRunTitle: 'Unit (Windows Server 2022)' 116 | condition: succeededOrFailed() 117 | 118 | - task: PublishPipelineArtifact@1 119 | displayName: 'Publish Test Artifact' 120 | inputs: 121 | targetPath: '$(buildFolderName)/$(testResultFolderName)/' 122 | artifactName: $(testArtifactName) 123 | 124 | - job: Code_Coverage 125 | displayName: 'Publish Code Coverage' 126 | dependsOn: Test_Unit_2022 127 | pool: 128 | vmImage: 'ubuntu-latest' 129 | timeoutInMinutes: '0' 130 | steps: 131 | - task: DownloadPipelineArtifact@2 132 | displayName: 'Download Pipeline Artifact' 133 | inputs: 134 | buildType: 'current' 135 | artifactName: $(buildArtifactName) 136 | targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' 137 | 138 | - task: DownloadPipelineArtifact@2 139 | displayName: 'Download Test Artifact' 140 | inputs: 141 | buildType: 'current' 142 | artifactName: $(testArtifactName) 143 | targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' 144 | 145 | - task: PublishCodeCoverageResults@1 146 | displayName: 'Publish Code Coverage to Azure DevOps' 147 | inputs: 148 | codeCoverageTool: 'JaCoCo' 149 | summaryFileLocation: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml' 150 | pathToSources: '$(Build.SourcesDirectory)/$(sourceFolderName)/' 151 | 152 | - script: | 153 | bash <(curl -s https://codecov.io/bash) -f "./$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml" 154 | displayName: 'Publish Code Coverage to Codecov.io' 155 | 156 | - job: Test_Integration_2022 157 | displayName: 'Integration (Windows Server 2022)' 158 | pool: 159 | vmImage: 'windows-2022' 160 | timeoutInMinutes: '0' 161 | steps: 162 | - task: DownloadPipelineArtifact@2 163 | displayName: 'Download Pipeline Artifact' 164 | inputs: 165 | buildType: 'current' 166 | artifactName: $(buildArtifactName) 167 | targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' 168 | 169 | - task: PowerShell@2 170 | name: configureWinRM 171 | displayName: 'Configure WinRM' 172 | inputs: 173 | targetType: 'inline' 174 | script: 'winrm quickconfig -quiet' 175 | pwsh: false 176 | 177 | - task: PowerShell@2 178 | name: test 179 | displayName: 'Run Integration Test' 180 | inputs: 181 | filePath: './build.ps1' 182 | arguments: "-Tasks test -PesterScript 'tests/Integration' -CodeCoverageThreshold 0" 183 | pwsh: false 184 | 185 | - task: PublishTestResults@2 186 | displayName: 'Publish Test Results' 187 | inputs: 188 | testResultsFormat: 'NUnit' 189 | testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' 190 | testRunTitle: 'Integration (Windows Server 2016)' 191 | condition: succeededOrFailed() 192 | 193 | - job: Test_Unit_2025 194 | displayName: 'Unit (Windows Server 2025)' 195 | pool: 196 | vmImage: 'windows-2025' 197 | timeoutInMinutes: '0' 198 | steps: 199 | - task: DownloadPipelineArtifact@2 200 | displayName: 'Download Pipeline Artifact' 201 | inputs: 202 | buildType: 'current' 203 | artifactName: $(buildArtifactName) 204 | targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' 205 | 206 | - task: PowerShell@2 207 | name: test 208 | displayName: 'Run Unit Test' 209 | inputs: 210 | filePath: './build.ps1' 211 | arguments: "-Tasks test -PesterScript 'tests/Unit'" 212 | pwsh: false 213 | 214 | - task: PublishTestResults@2 215 | displayName: 'Publish Test Results' 216 | inputs: 217 | testResultsFormat: 'NUnit' 218 | testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' 219 | testRunTitle: 'Unit (Windows Server 2025)' 220 | condition: succeededOrFailed() 221 | 222 | - job: Test_Integration_2025 223 | displayName: 'Integration (Windows Server 2025)' 224 | pool: 225 | vmImage: 'windows-2025' 226 | timeoutInMinutes: '0' 227 | steps: 228 | - task: DownloadPipelineArtifact@2 229 | displayName: 'Download Pipeline Artifact' 230 | inputs: 231 | buildType: 'current' 232 | artifactName: $(buildArtifactName) 233 | targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' 234 | 235 | - task: PowerShell@2 236 | name: configureWinRM 237 | displayName: 'Configure WinRM' 238 | inputs: 239 | targetType: 'inline' 240 | script: 'winrm quickconfig -quiet' 241 | pwsh: false 242 | 243 | - task: PowerShell@2 244 | name: test 245 | displayName: 'Run Integration Test' 246 | inputs: 247 | filePath: './build.ps1' 248 | arguments: "-Tasks test -PesterScript 'tests/Integration' -CodeCoverageThreshold 0" 249 | pwsh: false 250 | 251 | - task: PublishTestResults@2 252 | displayName: 'Publish Test Results' 253 | inputs: 254 | testResultsFormat: 'NUnit' 255 | testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' 256 | testRunTitle: 'Integration (Windows Server 2025)' 257 | condition: succeededOrFailed() 258 | 259 | - stage: Deploy 260 | dependsOn: Test 261 | condition: | 262 | and( 263 | succeeded(), 264 | or( 265 | eq(variables['Build.SourceBranch'], 'refs/heads/main'), 266 | startsWith(variables['Build.SourceBranch'], 'refs/tags/') 267 | ), 268 | contains(variables['System.TeamFoundationCollectionUri'], 'dsccommunity') 269 | ) 270 | jobs: 271 | - job: Deploy_Module 272 | displayName: 'Deploy Module' 273 | pool: 274 | vmImage: 'ubuntu-latest' 275 | steps: 276 | - task: DownloadPipelineArtifact@2 277 | displayName: 'Download Pipeline Artifact' 278 | inputs: 279 | buildType: 'current' 280 | artifactName: $(buildArtifactName) 281 | targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' 282 | 283 | - task: PowerShell@2 284 | name: publishRelease 285 | displayName: 'Publish Release' 286 | inputs: 287 | filePath: './build.ps1' 288 | arguments: '-tasks publish' 289 | pwsh: true 290 | env: 291 | GitHubToken: $(GitHubToken) 292 | GalleryApiToken: $(GalleryApiToken) 293 | ReleaseBranch: main 294 | MainGitBranch: main 295 | 296 | - task: PowerShell@2 297 | name: sendChangelogPR 298 | displayName: 'Send Changelog PR' 299 | inputs: 300 | filePath: './build.ps1' 301 | arguments: '-tasks Create_ChangeLog_GitHub_PR' 302 | pwsh: true 303 | env: 304 | GitHubToken: $(GitHubToken) 305 | ReleaseBranch: main 306 | MainGitBranch: main 307 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | Bootstrap and build script for PowerShell module CI/CD pipeline. 4 | 5 | .PARAMETER Tasks 6 | The task or tasks to run. The default value is '.' (runs the default task). 7 | 8 | .PARAMETER CodeCoverageThreshold 9 | The code coverage target threshold to uphold. Set to 0 to disable. 10 | The default value is '' (empty string). 11 | 12 | .PARAMETER BuildConfig 13 | Not yet written. 14 | 15 | .PARAMETER OutputDirectory 16 | Specifies the folder to build the artefact into. The default value is 'output'. 17 | 18 | .PARAMETER BuiltModuleSubdirectory 19 | Subdirectory name to build the module (under $OutputDirectory). The default 20 | value is '' (empty string). 21 | 22 | .PARAMETER RequiredModulesDirectory 23 | Can be a path (relative to $PSScriptRoot or absolute) to tell Resolve-Dependency 24 | and PSDepend where to save the required modules. It is also possible to use 25 | 'CurrentUser' och 'AllUsers' to install missing dependencies. You can override 26 | the value for PSDepend in the Build.psd1 build manifest. The default value is 27 | 'output/RequiredModules'. 28 | 29 | .PARAMETER PesterScript 30 | One or more paths that will override the Pester configuration in build 31 | configuration file when running the build task Invoke_Pester_Tests. 32 | 33 | If running Pester 5 test, use the alias PesterPath to be future-proof. 34 | 35 | .PARAMETER PesterTag 36 | Filter which tags to run when invoking Pester tests. This is used in the 37 | Invoke-Pester.pester.build.ps1 tasks. 38 | 39 | .PARAMETER PesterExcludeTag 40 | Filter which tags to exclude when invoking Pester tests. This is used in 41 | the Invoke-Pester.pester.build.ps1 tasks. 42 | 43 | .PARAMETER DscTestTag 44 | Filter which tags to run when invoking DSC Resource tests. This is used 45 | in the DscResource.Test.build.ps1 tasks. 46 | 47 | .PARAMETER DscTestExcludeTag 48 | Filter which tags to exclude when invoking DSC Resource tests. This is 49 | used in the DscResource.Test.build.ps1 tasks. 50 | 51 | .PARAMETER ResolveDependency 52 | Not yet written. 53 | 54 | .PARAMETER BuildInfo 55 | The build info object from ModuleBuilder. Defaults to an empty hashtable. 56 | 57 | .PARAMETER AutoRestore 58 | Not yet written. 59 | 60 | .PARAMETER UseModuleFast 61 | Specifies to use ModuleFast instead of PowerShellGet to resolve dependencies 62 | faster. 63 | 64 | .PARAMETER UsePSResourceGet 65 | Specifies to use PSResourceGet instead of PowerShellGet to resolve dependencies 66 | faster. This can also be configured in Resolve-Dependency.psd1. 67 | 68 | .PARAMETER UsePowerShellGetCompatibilityModule 69 | Specifies to use the compatibility module PowerShellGet. This parameter 70 | only works then the method of downloading dependencies is PSResourceGet. 71 | This can also be configured in Resolve-Dependency.psd1. 72 | #> 73 | [CmdletBinding()] 74 | param 75 | ( 76 | [Parameter(Position = 0)] 77 | [System.String[]] 78 | $Tasks = '.', 79 | 80 | [Parameter()] 81 | [System.String] 82 | $CodeCoverageThreshold = '', 83 | 84 | [Parameter()] 85 | [System.String] 86 | [ValidateScript( 87 | { Test-Path -Path $_ } 88 | )] 89 | $BuildConfig, 90 | 91 | [Parameter()] 92 | [System.String] 93 | $OutputDirectory = 'output', 94 | 95 | [Parameter()] 96 | [System.String] 97 | $BuiltModuleSubdirectory = '', 98 | 99 | [Parameter()] 100 | [System.String] 101 | $RequiredModulesDirectory = $(Join-Path 'output' 'RequiredModules'), 102 | 103 | [Parameter()] 104 | # This alias is to prepare for the rename of this parameter to PesterPath when Pester 4 support is removed 105 | [Alias('PesterPath')] 106 | [System.Object[]] 107 | $PesterScript, 108 | 109 | [Parameter()] 110 | [System.String[]] 111 | $PesterTag, 112 | 113 | [Parameter()] 114 | [System.String[]] 115 | $PesterExcludeTag, 116 | 117 | [Parameter()] 118 | [System.String[]] 119 | $DscTestTag, 120 | 121 | [Parameter()] 122 | [System.String[]] 123 | $DscTestExcludeTag, 124 | 125 | [Parameter()] 126 | [Alias('bootstrap')] 127 | [System.Management.Automation.SwitchParameter] 128 | $ResolveDependency, 129 | 130 | [Parameter(DontShow)] 131 | [AllowNull()] 132 | [System.Collections.Hashtable] 133 | $BuildInfo, 134 | 135 | [Parameter()] 136 | [System.Management.Automation.SwitchParameter] 137 | $AutoRestore, 138 | 139 | [Parameter()] 140 | [System.Management.Automation.SwitchParameter] 141 | $UseModuleFast, 142 | 143 | [Parameter()] 144 | [System.Management.Automation.SwitchParameter] 145 | $UsePSResourceGet, 146 | 147 | [Parameter()] 148 | [System.Management.Automation.SwitchParameter] 149 | $UsePowerShellGetCompatibilityModule 150 | ) 151 | 152 | <# 153 | The BEGIN block (at the end of this file) handles the Bootstrap of the Environment 154 | before Invoke-Build can run the tasks if the parameter ResolveDependency (or 155 | parameter alias Bootstrap) is specified. 156 | #> 157 | 158 | process 159 | { 160 | if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1') 161 | { 162 | # Only run the process block through InvokeBuild (look at the Begin block at the bottom of this script). 163 | return 164 | } 165 | 166 | # Execute the Build process from the .build.ps1 path. 167 | Push-Location -Path $PSScriptRoot -StackName 'BeforeBuild' 168 | 169 | try 170 | { 171 | Write-Host -Object "[build] Parsing defined tasks" -ForeGroundColor Magenta 172 | 173 | # Load the default BuildInfo if the parameter BuildInfo is not set. 174 | if (-not $PSBoundParameters.ContainsKey('BuildInfo')) 175 | { 176 | try 177 | { 178 | if (Test-Path -Path $BuildConfig) 179 | { 180 | $configFile = Get-Item -Path $BuildConfig 181 | 182 | Write-Host -Object "[build] Loading Configuration from $configFile" 183 | 184 | $BuildInfo = switch -Regex ($configFile.Extension) 185 | { 186 | # Native Support for PSD1 187 | '\.psd1' 188 | { 189 | if (-not (Get-Command -Name Import-PowerShellDataFile -ErrorAction SilentlyContinue)) 190 | { 191 | Import-Module -Name Microsoft.PowerShell.Utility -RequiredVersion 3.1.0.0 192 | } 193 | 194 | Import-PowerShellDataFile -Path $BuildConfig 195 | } 196 | 197 | # Support for yaml when module PowerShell-Yaml is available 198 | '\.[yaml|yml]' 199 | { 200 | Import-Module -Name 'powershell-yaml' -ErrorAction Stop 201 | 202 | ConvertFrom-Yaml -Yaml (Get-Content -Raw $configFile) 203 | } 204 | 205 | # Support for JSON and JSONC (by Removing comments) when module PowerShell-Yaml is available 206 | '\.[json|jsonc]' 207 | { 208 | $jsonFile = Get-Content -Raw -Path $configFile 209 | 210 | $jsonContent = $jsonFile -replace '(?m)\s*//.*?$' -replace '(?ms)/\*.*?\*/' 211 | 212 | # Yaml is superset of JSON. 213 | ConvertFrom-Yaml -Yaml $jsonContent 214 | } 215 | 216 | # Unknown extension, return empty hashtable. 217 | default 218 | { 219 | Write-Error -Message "Extension '$_' not supported. using @{}" 220 | 221 | @{ } 222 | } 223 | } 224 | } 225 | else 226 | { 227 | Write-Host -Object "Configuration file '$($BuildConfig.FullName)' not found" -ForegroundColor Red 228 | 229 | # No config file was found, return empty hashtable. 230 | $BuildInfo = @{ } 231 | } 232 | } 233 | catch 234 | { 235 | $logMessage = "Error loading Config '$($BuildConfig.FullName)'.`r`nAre you missing dependencies?`r`nMake sure you run './build.ps1 -ResolveDependency -tasks noop' before running build to restore the required modules." 236 | 237 | Write-Host -Object $logMessage -ForegroundColor Yellow 238 | 239 | $BuildInfo = @{ } 240 | 241 | Write-Error -Message $_.Exception.Message 242 | } 243 | } 244 | 245 | # If the Invoke-Build Task Header is specified in the Build Info, set it. 246 | if ($BuildInfo.TaskHeader) 247 | { 248 | Set-BuildHeader -Script ([scriptblock]::Create($BuildInfo.TaskHeader)) 249 | } 250 | 251 | <# 252 | Add BuildModuleOutput to PSModule Path environment variable. 253 | Moved here (not in begin block) because build file can contains BuiltSubModuleDirectory value. 254 | #> 255 | if ($BuiltModuleSubdirectory) 256 | { 257 | if (-not (Split-Path -IsAbsolute -Path $BuiltModuleSubdirectory)) 258 | { 259 | $BuildModuleOutput = Join-Path -Path $OutputDirectory -ChildPath $BuiltModuleSubdirectory 260 | } 261 | else 262 | { 263 | $BuildModuleOutput = $BuiltModuleSubdirectory 264 | } 265 | } # test if BuiltModuleSubDirectory set in build config file 266 | elseif ($BuildInfo.ContainsKey('BuiltModuleSubDirectory')) 267 | { 268 | $BuildModuleOutput = Join-Path -Path $OutputDirectory -ChildPath $BuildInfo['BuiltModuleSubdirectory'] 269 | } 270 | else 271 | { 272 | $BuildModuleOutput = $OutputDirectory 273 | } 274 | 275 | # Pre-pending $BuildModuleOutput folder to PSModulePath to resolve built module from this folder. 276 | if ($powerShellModulePaths -notcontains $BuildModuleOutput) 277 | { 278 | Write-Host -Object "[build] Pre-pending '$BuildModuleOutput' folder to PSModulePath" -ForegroundColor Green 279 | 280 | $env:PSModulePath = $BuildModuleOutput + [System.IO.Path]::PathSeparator + $env:PSModulePath 281 | } 282 | 283 | <# 284 | Import Tasks from modules via their exported aliases when defined in Build Manifest. 285 | https://github.com/nightroman/Invoke-Build/tree/master/Tasks/Import#example-2-import-from-a-module-with-tasks 286 | #> 287 | if ($BuildInfo.ContainsKey('ModuleBuildTasks')) 288 | { 289 | foreach ($module in $BuildInfo['ModuleBuildTasks'].Keys) 290 | { 291 | try 292 | { 293 | Write-Host -Object "Importing tasks from module $module" -ForegroundColor DarkGray 294 | 295 | $loadedModule = Import-Module -Name $module -PassThru -ErrorAction Stop 296 | 297 | foreach ($TaskToExport in $BuildInfo['ModuleBuildTasks'].($module)) 298 | { 299 | $loadedModule.ExportedAliases.GetEnumerator().Where{ 300 | Write-Host -Object "`t Loading $($_.Key)..." -ForegroundColor DarkGray 301 | 302 | # Using -like to support wildcard. 303 | $_.Key -like $TaskToExport 304 | }.ForEach{ 305 | # Dot-sourcing the Tasks via their exported aliases. 306 | . (Get-Alias $_.Key) 307 | } 308 | } 309 | } 310 | catch 311 | { 312 | Write-Host -Object "Could not load tasks for module $module." -ForegroundColor Red 313 | 314 | Write-Error -Message $_ 315 | } 316 | } 317 | } 318 | 319 | # Loading Build Tasks defined in the .build/ folder (will override the ones imported above if same task name). 320 | Get-ChildItem -Path '.build/' -Recurse -Include '*.ps1' -ErrorAction Ignore | 321 | ForEach-Object { 322 | "Importing file $($_.BaseName)" | Write-Verbose 323 | 324 | . $_.FullName 325 | } 326 | 327 | # Synopsis: Empty task, useful to test the bootstrap process. 328 | task noop { } 329 | 330 | # Define default task sequence ("."), can be overridden in the $BuildInfo. 331 | task . { 332 | Write-Build -Object 'No sequence currently defined for the default task' -ForegroundColor Yellow 333 | } 334 | 335 | Write-Host -Object 'Adding Workflow from configuration:' -ForegroundColor DarkGray 336 | 337 | # Load Invoke-Build task sequences/workflows from $BuildInfo. 338 | foreach ($workflow in $BuildInfo.BuildWorkflow.keys) 339 | { 340 | Write-Verbose -Message "Creating Build Workflow '$Workflow' with tasks $($BuildInfo.BuildWorkflow.($Workflow) -join ', ')." 341 | 342 | $workflowItem = $BuildInfo.BuildWorkflow.($workflow) 343 | 344 | if ($workflowItem.Trim() -match '^\{(?[\w\W]*)\}$') 345 | { 346 | $workflowItem = [ScriptBlock]::Create($Matches['sb']) 347 | } 348 | 349 | Write-Host -Object " +-> $workflow" -ForegroundColor DarkGray 350 | 351 | task $workflow $workflowItem 352 | } 353 | 354 | Write-Host -Object "[build] Executing requested workflow: $($Tasks -join ', ')" -ForeGroundColor Magenta 355 | 356 | } 357 | finally 358 | { 359 | Pop-Location -StackName 'BeforeBuild' 360 | } 361 | } 362 | 363 | begin 364 | { 365 | # Find build config if not specified. 366 | if (-not $BuildConfig) 367 | { 368 | $config = Get-ChildItem -Path "$PSScriptRoot\*" -Include 'build.y*ml', 'build.psd1', 'build.json*' -ErrorAction Ignore 369 | 370 | if (-not $config -or ($config -is [System.Array] -and $config.Length -le 0)) 371 | { 372 | throw 'No build configuration found. Specify path via parameter BuildConfig.' 373 | } 374 | elseif ($config -is [System.Array]) 375 | { 376 | if ($config.Length -gt 1) 377 | { 378 | throw 'More than one build configuration found. Specify which path to use via parameter BuildConfig.' 379 | } 380 | 381 | $BuildConfig = $config[0] 382 | } 383 | else 384 | { 385 | $BuildConfig = $config 386 | } 387 | } 388 | 389 | # Bootstrapping the environment before using Invoke-Build as task runner 390 | 391 | if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1') 392 | { 393 | Write-Host -Object "[pre-build] Starting Build Init" -ForegroundColor Green 394 | 395 | Push-Location $PSScriptRoot -StackName 'BuildModule' 396 | } 397 | 398 | if ($RequiredModulesDirectory -in @('CurrentUser', 'AllUsers')) 399 | { 400 | # Installing modules instead of saving them. 401 | Write-Host -Object "[pre-build] Required Modules will be installed to the PowerShell module path that is used for $RequiredModulesDirectory." -ForegroundColor Green 402 | 403 | <# 404 | The variable $PSDependTarget will be used below when building the splatting 405 | variable before calling Resolve-Dependency.ps1, unless overridden in the 406 | file Resolve-Dependency.psd1. 407 | #> 408 | $PSDependTarget = $RequiredModulesDirectory 409 | } 410 | else 411 | { 412 | if (-not (Split-Path -IsAbsolute -Path $OutputDirectory)) 413 | { 414 | $OutputDirectory = Join-Path -Path $PSScriptRoot -ChildPath $OutputDirectory 415 | } 416 | 417 | # Resolving the absolute path to save the required modules to. 418 | if (-not (Split-Path -IsAbsolute -Path $RequiredModulesDirectory)) 419 | { 420 | $RequiredModulesDirectory = Join-Path -Path $PSScriptRoot -ChildPath $RequiredModulesDirectory 421 | } 422 | 423 | # Create the output/modules folder if not exists, or resolve the Absolute path otherwise. 424 | if (Resolve-Path -Path $RequiredModulesDirectory -ErrorAction SilentlyContinue) 425 | { 426 | Write-Debug -Message "[pre-build] Required Modules path already exist at $RequiredModulesDirectory" 427 | 428 | $requiredModulesPath = Convert-Path -Path $RequiredModulesDirectory 429 | } 430 | else 431 | { 432 | Write-Host -Object "[pre-build] Creating required modules directory $RequiredModulesDirectory." -ForegroundColor Green 433 | 434 | $requiredModulesPath = (New-Item -ItemType Directory -Force -Path $RequiredModulesDirectory).FullName 435 | } 436 | 437 | $powerShellModulePaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator 438 | 439 | # Pre-pending $requiredModulesPath folder to PSModulePath to resolve from this folder FIRST. 440 | if ($RequiredModulesDirectory -notin @('CurrentUser', 'AllUsers') -and 441 | ($powerShellModulePaths -notcontains $RequiredModulesDirectory)) 442 | { 443 | Write-Host -Object "[pre-build] Pre-pending '$RequiredModulesDirectory' folder to PSModulePath" -ForegroundColor Green 444 | 445 | $env:PSModulePath = $RequiredModulesDirectory + [System.IO.Path]::PathSeparator + $env:PSModulePath 446 | } 447 | 448 | $powerShellYamlModule = Get-Module -Name 'powershell-yaml' -ListAvailable 449 | $invokeBuildModule = Get-Module -Name 'InvokeBuild' -ListAvailable 450 | $psDependModule = Get-Module -Name 'PSDepend' -ListAvailable 451 | 452 | # Checking if the user should -ResolveDependency. 453 | if (-not ($powerShellYamlModule -and $invokeBuildModule -and $psDependModule) -and -not $ResolveDependency) 454 | { 455 | if ($AutoRestore -or -not $PSBoundParameters.ContainsKey('Tasks') -or $Tasks -contains 'build') 456 | { 457 | Write-Host -Object "[pre-build] Dependency missing, running './build.ps1 -ResolveDependency -Tasks noop' for you `r`n" -ForegroundColor Yellow 458 | 459 | $ResolveDependency = $true 460 | } 461 | else 462 | { 463 | Write-Warning -Message "Some required Modules are missing, make sure you first run with the '-ResolveDependency' parameter. Running 'build.ps1 -ResolveDependency -Tasks noop' will pull required modules without running the build task." 464 | } 465 | } 466 | 467 | <# 468 | The variable $PSDependTarget will be used below when building the splatting 469 | variable before calling Resolve-Dependency.ps1, unless overridden in the 470 | file Resolve-Dependency.psd1. 471 | #> 472 | $PSDependTarget = $requiredModulesPath 473 | } 474 | 475 | if ($ResolveDependency) 476 | { 477 | Write-Host -Object "[pre-build] Resolving dependencies using preferred method." -ForegroundColor Green 478 | 479 | $resolveDependencyParams = @{ } 480 | 481 | # If BuildConfig is a Yaml file, bootstrap powershell-yaml via ResolveDependency. 482 | if ($BuildConfig -match '\.[yaml|yml]$') 483 | { 484 | $resolveDependencyParams.Add('WithYaml', $true) 485 | } 486 | 487 | $resolveDependencyAvailableParams = (Get-Command -Name '.\Resolve-Dependency.ps1').Parameters.Keys 488 | 489 | foreach ($cmdParameter in $resolveDependencyAvailableParams) 490 | { 491 | # The parameter has been explicitly used for calling the .build.ps1 492 | if ($MyInvocation.BoundParameters.ContainsKey($cmdParameter)) 493 | { 494 | $paramValue = $MyInvocation.BoundParameters.Item($cmdParameter) 495 | 496 | Write-Debug " adding $cmdParameter :: $paramValue [from user-provided parameters to Build.ps1]" 497 | 498 | $resolveDependencyParams.Add($cmdParameter, $paramValue) 499 | } 500 | # Use defaults parameter value from Build.ps1, if any 501 | else 502 | { 503 | $paramValue = Get-Variable -Name $cmdParameter -ValueOnly -ErrorAction Ignore 504 | 505 | if ($paramValue) 506 | { 507 | Write-Debug " adding $cmdParameter :: $paramValue [from default Build.ps1 variable]" 508 | 509 | $resolveDependencyParams.Add($cmdParameter, $paramValue) 510 | } 511 | } 512 | } 513 | 514 | Write-Host -Object "[pre-build] Starting bootstrap process." -ForegroundColor Green 515 | 516 | .\Resolve-Dependency.ps1 @resolveDependencyParams 517 | } 518 | 519 | if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1') 520 | { 521 | Write-Verbose -Message "Bootstrap completed. Handing back to InvokeBuild." 522 | 523 | if ($PSBoundParameters.ContainsKey('ResolveDependency')) 524 | { 525 | Write-Verbose -Message "Dependency already resolved. Removing task." 526 | 527 | $null = $PSBoundParameters.Remove('ResolveDependency') 528 | } 529 | 530 | Write-Host -Object "[build] Starting build with InvokeBuild." -ForegroundColor Green 531 | 532 | Invoke-Build @PSBoundParameters -Task $Tasks -File $MyInvocation.MyCommand.Path 533 | 534 | Pop-Location -StackName 'BuildModule' 535 | 536 | return 537 | } 538 | } 539 | -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | #################################################### 3 | # Pipeline Configuration # 4 | #################################################### 5 | BuildWorkflow: 6 | '.': 7 | - build 8 | - test 9 | 10 | build: 11 | - Clean 12 | - Build_Module_ModuleBuilder 13 | - Build_NestedModules_ModuleBuilder 14 | - Create_Changelog_Release_Output 15 | 16 | docs: 17 | - Generate_Conceptual_Help 18 | - Generate_Wiki_Content 19 | - Generate_Wiki_Sidebar 20 | - Clean_Markdown_Metadata 21 | - Package_Wiki_Content 22 | 23 | pack: 24 | - build 25 | - docs 26 | - package_module_nupkg 27 | 28 | hqrmtest: 29 | - DscResource_Tests_Stop_On_Fail 30 | 31 | test: 32 | - Pester_Tests_Stop_On_Fail 33 | - Pester_If_Code_Coverage_Under_Threshold 34 | 35 | publish: 36 | - publish_module_to_gallery 37 | - Publish_Release_To_GitHub 38 | - Publish_GitHub_Wiki_Content 39 | - Create_ChangeLog_GitHub_PR 40 | 41 | #################################################### 42 | # ModuleBuilder Configuration # 43 | #################################################### 44 | CopyPaths: 45 | - en-US 46 | - DSCResources 47 | - Modules 48 | Encoding: UTF8 49 | VersionedOutputDirectory: true 50 | BuiltModuleSubdirectory: builtModule 51 | 52 | ModuleBuildTasks: 53 | Sampler: 54 | - '*.build.Sampler.ib.tasks' 55 | Sampler.GitHubTasks: 56 | - '*.ib.tasks' 57 | DscResource.DocGenerator: 58 | - 'Task.*' 59 | DscResource.Test: 60 | - 'Task.*' 61 | 62 | TaskHeader: | 63 | param($Path) 64 | "" 65 | "=" * 79 66 | Write-Build Cyan "`t`t`t$($Task.Name.replace("_"," ").ToUpper())" 67 | Write-Build DarkGray "$(Get-BuildSynopsis $Task)" 68 | "-" * 79 69 | Write-Build DarkGray " $Path" 70 | Write-Build DarkGray " $($Task.InvocationInfo.ScriptName):$($Task.InvocationInfo.ScriptLineNumber)" 71 | "" 72 | 73 | #################################################### 74 | # Dependent Modules Configuration (Sampler) # 75 | #################################################### 76 | NestedModule: 77 | DscResource.Common: 78 | CopyOnly: true 79 | Path: ./output/RequiredModules/DscResource.Common 80 | AddToManifest: false 81 | Exclude: PSGetModuleInfo.xml 82 | 83 | #################################################### 84 | # Pester Configuration (Sampler) # 85 | #################################################### 86 | 87 | Pester: 88 | # Pester 4 configuration 89 | Script: 90 | - tests/Unit 91 | ExcludeTag: 92 | Tag: 93 | CodeCoverageOutputFile: JaCoCo_coverage.xml 94 | CodeCoverageOutputFileEncoding: ascii 95 | CodeCoverageThreshold: 85 96 | 97 | # Pester 5 configuration 98 | Configuration: 99 | Run: 100 | Path: 101 | - tests/Unit 102 | Output: 103 | Verbosity: Detailed 104 | StackTraceVerbosity: Full 105 | CIFormat: Auto 106 | CodeCoverage: 107 | CoveragePercentTarget: 80 108 | OutputPath: JaCoCo_coverage.xml 109 | OutputEncoding: ascii 110 | UseBreakpoints: false 111 | TestResult: 112 | OutputFormat: NUnitXML 113 | OutputEncoding: ascii 114 | ExcludeFromCodeCoverage: 115 | - Modules/DscResource.Common 116 | 117 | #################################################### 118 | # Pester Configuration (DscResource.Test) # 119 | #################################################### 120 | DscTest: 121 | OutputFormat: NUnitXML 122 | ExcludeTag: 123 | - 'Common Tests - New Error-Level Script Analyzer Rules' 124 | ExcludeSourceFile: 125 | - output 126 | ExcludeModuleFile: 127 | - Modules/DscResource.Common 128 | MainGitBranch: main 129 | 130 | #################################################### 131 | # PSDepend Configuration # 132 | #################################################### 133 | Resolve-Dependency: 134 | Gallery: 'PSGallery' 135 | AllowPrerelease: false 136 | Verbose: false 137 | 138 | #################################################### 139 | # GitHub Configuration # 140 | #################################################### 141 | GitHubConfig: 142 | GitHubFilesToAdd: 143 | - 'CHANGELOG.md' 144 | ReleaseAssets: 145 | - output/WikiContent.zip 146 | GitHubConfigUserName: dscbot # cSpell: disable-line 147 | GitHubConfigUserEmail: dsccommunity@outlook.com 148 | UpdateChangelogOnPrerelease: false 149 | 150 | #################################################### 151 | # DscResource.DocGenerator Configuration # 152 | #################################################### 153 | DscResource.DocGenerator: 154 | Generate_Conceptual_Help: 155 | MarkdownCodeRegularExpression: 156 | - '\`(.+?)\`' # Match inline code-block 157 | - '\\(\\)' # Match escaped backslash 158 | - '\[[^\[]+\]\((.+?)\)' # Match markdown URL 159 | - '_(.+?)_' # Match Italic (underscore) 160 | - '\*\*(.+?)\*\*' # Match bold 161 | - '\*(.+?)\*' # Match Italic (asterisk) 162 | Publish_GitHub_Wiki_Content: 163 | Debug: false 164 | Generate_Markdown_For_DSC_Resources: 165 | MofResourceMetadata: 166 | Type: MofResource 167 | Category: Resources 168 | ClassResourceMetadata: 169 | Type: ClassResource 170 | Category: Resources 171 | CompositeResourceMetadata: 172 | Type: CompositeResource 173 | Category: Resources 174 | Generate_Wiki_Sidebar: 175 | Debug: false 176 | AlwaysOverwrite: true 177 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: no 3 | # main should be the baseline for reporting 4 | branch: main 5 | 6 | comment: 7 | layout: "reach, diff, flags, files" 8 | behavior: default 9 | 10 | coverage: 11 | range: 50..80 12 | round: down 13 | precision: 0 14 | 15 | status: 16 | project: 17 | default: 18 | # Set the overall project code coverage requirement to 70% 19 | target: 70 20 | patch: 21 | default: 22 | # Set the pull request requirement to not regress overall coverage by more than 5% 23 | # and let codecov.io set the goal for the code changed in the patch. 24 | target: auto 25 | threshold: 5 26 | 27 | fixes: 28 | - '^\d+\.\d+\.\d+::source' # move path "X.Y.Z" => "source" 29 | -------------------------------------------------------------------------------- /source/Build.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | Path = 'FileContentDsc.psd1' 3 | } 4 | # Waiting for ModuleBuilder to do away with this file 5 | # when all parameters are provided to the function 6 | -------------------------------------------------------------------------------- /source/DSCResources/DSC_IniSettingsFile/DSC_IniSettingsFile.psm1: -------------------------------------------------------------------------------- 1 | Set-StrictMode -Version 'Latest' 2 | 3 | $modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules' 4 | 5 | # Import the Networking Common Modules 6 | Import-Module -Name (Join-Path -Path $modulePath ` 7 | -ChildPath (Join-Path -Path 'FileContentDsc.Common' ` 8 | -ChildPath 'FileContentDsc.Common.psm1')) 9 | 10 | Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common') 11 | 12 | # Import Localization Strings 13 | $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' 14 | 15 | <# 16 | .SYNOPSIS 17 | Retrieves the current state of the INI settings file entry. 18 | 19 | .PARAMETER Path 20 | The path to the INI settings file to set the entry in. 21 | 22 | .PARAMETER Section 23 | The section to add or set the entry in. 24 | 25 | .PARAMETER Key 26 | The name of the key to add or set in the section. 27 | #> 28 | function Get-TargetResource 29 | { 30 | [OutputType([Hashtable])] 31 | [CmdletBinding()] 32 | param 33 | ( 34 | [Parameter(Mandatory = $true)] 35 | [ValidateNotNullOrEmpty()] 36 | [System.String] 37 | $Path, 38 | 39 | [Parameter(Mandatory = $true)] 40 | [ValidateNotNullOrEmpty()] 41 | [System.String] 42 | $Section, 43 | 44 | [Parameter(Mandatory = $true)] 45 | [ValidateNotNullOrEmpty()] 46 | [System.String] 47 | $Key 48 | ) 49 | 50 | Assert-ParametersValid @PSBoundParameters 51 | 52 | Write-Verbose -Message ($script:localizedData.GetIniSettingMessage -f ` 53 | $Path, $Section, $Key) 54 | 55 | $text = Get-IniSettingFileValue @PSBoundParameters 56 | 57 | return @{ 58 | Path = $Path 59 | Section = $Section 60 | Key = $Key 61 | Type = 'Text' 62 | Text = $text 63 | } 64 | } 65 | 66 | <# 67 | .SYNOPSIS 68 | Sets the value of an entry in an INI settings file. 69 | 70 | .PARAMETER Path 71 | The path to the INI settings file to set the entry in. 72 | 73 | .PARAMETER Section 74 | The section to add or set the entry in. 75 | 76 | .PARAMETER Key 77 | The name of the key to add or set in the section. 78 | 79 | .PARAMETER Type 80 | Specifies the value type that contains the value to set the entry to. Defaults to 'Text'. 81 | 82 | .PARAMETER Text 83 | The text to set the entry value to. 84 | Only used when Type is set to 'Text'. 85 | 86 | .PARAMETER Secret 87 | The secret text to set the entry value to. 88 | Only used when Type is set to 'Secret'. 89 | #> 90 | function Set-TargetResource 91 | { 92 | [CmdletBinding()] 93 | param 94 | ( 95 | [Parameter(Mandatory = $true)] 96 | [ValidateNotNullOrEmpty()] 97 | [System.String] 98 | $Path, 99 | 100 | [Parameter(Mandatory = $true)] 101 | [ValidateNotNullOrEmpty()] 102 | [System.String] 103 | $Section, 104 | 105 | [Parameter(Mandatory = $true)] 106 | [ValidateNotNullOrEmpty()] 107 | [System.String] 108 | $Key, 109 | 110 | [Parameter()] 111 | [ValidateSet('Text', 'Secret')] 112 | [System.String] 113 | $Type = 'Text', 114 | 115 | [Parameter()] 116 | [System.String] 117 | $Text, 118 | 119 | [Parameter()] 120 | [System.Management.Automation.PSCredential] 121 | [System.Management.Automation.Credential()] 122 | $Secret 123 | ) 124 | 125 | Assert-ParametersValid @PSBoundParameters 126 | 127 | if (-not (Test-Path -Path $Path)) 128 | { 129 | Out-File -FilePath $Path -Force 130 | } 131 | 132 | if ($Type -eq 'Secret') 133 | { 134 | Write-Verbose -Message ($script:localizedData.SetIniSettingSecretMessage -f ` 135 | $Path, $Section, $Key) 136 | 137 | $Text = $Secret.GetNetworkCredential().Password 138 | $null = $PSBoundParameters.Remove('Secret') 139 | } 140 | else 141 | { 142 | Write-Verbose -Message ($script:localizedData.SetIniSettingTextMessage -f ` 143 | $Path, $Section, $Key, $Text) 144 | } # if 145 | 146 | # Prepare the for the PSBoundParameters to be splatted 147 | $null = $PSBoundParameters.Remove('Type') 148 | $null = $PSBoundParameters.Add('Value',$Text) 149 | $null = $PSBoundParameters.Remove('Text') 150 | 151 | Set-IniSettingFileValue @PSBoundParameters 152 | } 153 | 154 | <# 155 | .SYNOPSIS 156 | Tests the value of an entry in an INI settings file. 157 | 158 | .PARAMETER Path 159 | The path to the INI settings file to set the entry in. 160 | 161 | .PARAMETER Section 162 | The section to add or set the entry in. 163 | 164 | .PARAMETER Key 165 | The name of the key to add or set in the section. 166 | 167 | .PARAMETER Type 168 | Specifies the value type that contains the value to set the entry to. Defaults to 'Text'. 169 | 170 | .PARAMETER Text 171 | The text to set the entry value to. 172 | Only used when Type is set to 'Text'. 173 | 174 | .PARAMETER Secret 175 | The secret text to set the entry value to. 176 | Only used when Type is set to 'Secret'. 177 | #> 178 | function Test-TargetResource 179 | { 180 | [OutputType([System.Boolean])] 181 | [CmdletBinding()] 182 | param 183 | ( 184 | [Parameter(Mandatory = $true)] 185 | [ValidateNotNullOrEmpty()] 186 | [System.String] 187 | $Path, 188 | 189 | [Parameter(Mandatory = $true)] 190 | [ValidateNotNullOrEmpty()] 191 | [System.String] 192 | $Section, 193 | 194 | [Parameter(Mandatory = $true)] 195 | [ValidateNotNullOrEmpty()] 196 | [System.String] 197 | $Key, 198 | 199 | [Parameter()] 200 | [ValidateSet('Text', 'Secret')] 201 | [System.String] 202 | $Type = 'Text', 203 | 204 | [Parameter()] 205 | [System.String] 206 | $Text, 207 | 208 | [Parameter()] 209 | [System.Management.Automation.PSCredential] 210 | [System.Management.Automation.Credential()] 211 | $Secret 212 | ) 213 | 214 | Assert-ParametersValid @PSBoundParameters 215 | 216 | # Check if file being managed exists. If not return $False. 217 | if (-not (Test-Path -Path $Path)) 218 | { 219 | return $false 220 | } 221 | 222 | if ($Type -eq 'Secret') 223 | { 224 | $Text = $Secret.GetNetworkCredential().Password 225 | } # if 226 | 227 | # Prepare the PSBoundParameters for splat 228 | $null = $PSBoundParameters.Remove('Type') 229 | $null = $PSBoundParameters.Remove('Text') 230 | $null = $PSBoundParameters.Remove('Secret') 231 | 232 | if ((Get-IniSettingFileValue @PSBoundParameters) -eq $Text) 233 | { 234 | Write-Verbose -Message ($script:localizedData.IniSettingMatchesMessage -f ` 235 | $Path, $Section, $Key) 236 | 237 | return $true 238 | } 239 | else 240 | { 241 | Write-Verbose -Message ($script:localizedData.IniSettingMismatchMessage -f ` 242 | $Path, $Section, $Key) 243 | 244 | return $false 245 | } # if 246 | } 247 | 248 | <# 249 | .SYNOPSIS 250 | Validates the parameters that have been passed are valid. 251 | If they are not valid then an exception will be thrown. 252 | 253 | .PARAMETER Path 254 | The path to the INI settings file to set the entry in. 255 | 256 | .PARAMETER Section 257 | The section to add or set the entry in. 258 | 259 | .PARAMETER Key 260 | The name of the key to add or set in the section. 261 | 262 | .PARAMETER Type 263 | Specifies the value type that contains the value to set the entry to. Defaults to 'Text'. 264 | 265 | .PARAMETER Text 266 | The text to set the entry value to. 267 | Only used when Type is set to 'Text'. 268 | 269 | .PARAMETER Secret 270 | The secret text to set the entry value to. 271 | Only used when Type is set to 'Secret'. 272 | #> 273 | function Assert-ParametersValid 274 | { 275 | [CmdletBinding()] 276 | param 277 | ( 278 | [Parameter(Mandatory = $true)] 279 | [ValidateNotNullOrEmpty()] 280 | [System.String] 281 | $Path, 282 | 283 | [Parameter(Mandatory = $true)] 284 | [ValidateNotNullOrEmpty()] 285 | [System.String] 286 | $Section, 287 | 288 | [Parameter(Mandatory = $true)] 289 | [ValidateNotNullOrEmpty()] 290 | [System.String] 291 | $Key, 292 | 293 | [Parameter()] 294 | [ValidateSet('Text', 'Secret')] 295 | [System.String] 296 | $Type = 'Text', 297 | 298 | [Parameter()] 299 | [System.String] 300 | $Text, 301 | 302 | [Parameter()] 303 | [System.Management.Automation.PSCredential] 304 | [System.Management.Automation.Credential()] 305 | $Secret 306 | ) 307 | 308 | # Does the file's parent path exist? 309 | $parentPath = Split-Path -Path $Path -Parent 310 | 311 | if (-not (Test-Path -Path $parentPath)) 312 | { 313 | New-InvalidArgumentException ` 314 | -Message ($script:localizedData.FileParentNotFoundError -f $parentPath) ` 315 | -ArgumentName 'Path' 316 | } # if 317 | } 318 | 319 | Export-ModuleMember -Function *-TargetResource 320 | -------------------------------------------------------------------------------- /source/DSCResources/DSC_IniSettingsFile/DSC_IniSettingsFile.schema.mof: -------------------------------------------------------------------------------- 1 | [ClassVersion("1.0.0.0"), FriendlyName("IniSettingsFile")] 2 | class DSC_IniSettingsFile : OMI_BaseResource 3 | { 4 | [Key, Description("The path to the INI settings file to set the entry in.")] String Path; 5 | [Key, Description("The section to add or set the entry in.")] String Section; 6 | [Key, Description("The name of the key to add or set in the section.")] String Key; 7 | [Write, Description("Specifies the value type that contains the value to set the entry to. Defaults to 'Text'."),ValueMap{"Text", "Secret"},Values{"Text", "Secret"}] String Type; 8 | [Write, Description("The text to set the entry value to. Only used when Type is set to 'Text'.")] String Text; 9 | [write, Description("The secret text to set the entry value to. Only used when Type is set to 'Secret'."),EmbeddedInstance("MSFT_Credential")] String Secret; 10 | }; 11 | -------------------------------------------------------------------------------- /source/DSCResources/DSC_IniSettingsFile/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | The resource is used to add, set or clear entries in Windows INI 4 | settings files. 5 | -------------------------------------------------------------------------------- /source/DSCResources/DSC_IniSettingsFile/en-US/DSC_IniSettingsFile.schema.mfl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsccommunity/FileContentDsc/42e61eb6b752cf747c6bb352357046bbb37d95f1/source/DSCResources/DSC_IniSettingsFile/en-US/DSC_IniSettingsFile.schema.mfl -------------------------------------------------------------------------------- /source/DSCResources/DSC_IniSettingsFile/en-US/DSC_IniSettingsFile.strings.psd1: -------------------------------------------------------------------------------- 1 | # Localized resources for DSC_ReplaceText 2 | 3 | ConvertFrom-StringData @' 4 | GetIniSettingMessage = Reading the entry '{1}' key '{2}' from INI settings file '{0}'. 5 | SetIniSettingTextMessage = Setting the entry '{1}' key '{2}' to '{3}' in INI settings file '{0}'. 6 | SetIniSettingSecretMessage = Setting the entry '{1}' key '{2}' to secret text in INI settings file '{0}'. 7 | IniSettingMatchesMessage = The entry '{1}' key '{2}' in INI settings file '{0}' is in the correct state. Change not required. 8 | IniSettingMismatchMessage = The entry '{1}' key '{2}' in INI settings file '{0}' is not in the correct state. Change required. 9 | FileParentNotFoundError = File parent path '{0}' not found. 10 | '@ 11 | -------------------------------------------------------------------------------- /source/DSCResources/DSC_KeyValuePairFile/DSC_KeyValuePairFile.psm1: -------------------------------------------------------------------------------- 1 | Set-StrictMode -Version 'Latest' 2 | 3 | $modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules' 4 | 5 | # Import the Networking Common Modules 6 | Import-Module -Name (Join-Path -Path $modulePath ` 7 | -ChildPath (Join-Path -Path 'FileContentDsc.Common' ` 8 | -ChildPath 'FileContentDsc.Common.psm1')) 9 | 10 | Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common') 11 | 12 | # Import Localization Strings 13 | $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' 14 | 15 | <# 16 | .SYNOPSIS 17 | Retrieves the current state of a key in a key value pair text file. 18 | 19 | .PARAMETER Path 20 | The path to the key value pair text file. 21 | 22 | .PARAMETER Name 23 | The name of the key. 24 | #> 25 | function Get-TargetResource 26 | { 27 | [OutputType([Hashtable])] 28 | [CmdletBinding()] 29 | param 30 | ( 31 | [Parameter(Mandatory = $true)] 32 | [ValidateNotNullOrEmpty()] 33 | [System.String] 34 | $Path, 35 | 36 | [Parameter(Mandatory = $true)] 37 | [ValidateNotNullOrEmpty()] 38 | [System.String] 39 | $Name 40 | ) 41 | 42 | Assert-ParametersValid @PSBoundParameters 43 | 44 | $ensure = 'Absent' 45 | $text = $null 46 | $fileEncoding = $null 47 | 48 | if (Test-Path -Path $Path) 49 | { 50 | $fileContent = Get-Content -Path $Path -Raw 51 | $fileEncoding = Get-FileEncoding -Path $Path 52 | 53 | if ($null -ne $fileContent) 54 | { 55 | Write-Verbose -Message ($script:localizedData.SearchForKeyMessage -f $Path, $Name) 56 | 57 | # Setup the Regex Options that will be used 58 | $regExOptions = [System.Text.RegularExpressions.RegexOptions]::Multiline 59 | 60 | # Search the key that matches the requested key 61 | $results = [regex]::Matches($fileContent, "^[\s]*$Name=([^\n\r]*)", $regExOptions) 62 | 63 | if ($results.Count -eq 0) 64 | { 65 | # No matches found 66 | Write-Verbose -Message ($script:localizedData.KeyNotFoundMessage -f $Path, $Name) 67 | } 68 | else 69 | { 70 | # One of more key value pairs were found 71 | $ensure = 'Present' 72 | $textValues = @() 73 | 74 | foreach ($match in $results) 75 | { 76 | $textValues += $match.Groups[1].Value 77 | } 78 | 79 | $text = ($textValues -join ',') 80 | 81 | Write-Verbose -Message ($script:localizedData.KeyFoundMessage -f $Path, $Name, $text) 82 | } # if 83 | } 84 | else 85 | { 86 | Write-Verbose -Message ($script:localizedData.KeyValuePairFileIsEmpty -f $Path) 87 | } 88 | } 89 | else 90 | { 91 | Write-Verbose -Message ($script:localizedData.KeyValuePairFileNotFound -f $Path) 92 | } 93 | 94 | return @{ 95 | Path = $Path 96 | Name = $Name 97 | Encoding = $fileEncoding 98 | Ensure = $ensure 99 | Type = 'Text' 100 | Text = $text 101 | IgnoreNameCase = $false 102 | IgnoreValueCase = $false 103 | } 104 | } 105 | 106 | <# 107 | .SYNOPSIS 108 | Sets the current state of a key in a key value pair text file. 109 | 110 | .PARAMETER Path 111 | The path to the key value pair text file. 112 | 113 | .PARAMETER Name 114 | The name of the key. 115 | 116 | .PARAMETER Ensure 117 | Specifies the if the key value pair with the specified key should exist in the file. 118 | 119 | .PARAMETER Type 120 | Specifies the value type to use as the replacement string. Defaults to 'Text'. 121 | 122 | .PARAMETER Text 123 | The text to replace the value with in the identified key. 124 | Only used when Type is set to 'Text'. 125 | 126 | .PARAMETER Secret 127 | The secret text to replace the value with in the identified key. 128 | Only used when Type is set to 'Secret'. 129 | 130 | .PARAMETER IgnoreNameCase 131 | Ignore the case of the name of the key. Defaults to $False. 132 | 133 | .PARAMETER IgnoreValueCase 134 | Ignore the case of any text or secret when determining if it they need to be updated. 135 | Defaults to $False. 136 | 137 | .PARAMETER Encoding 138 | Specifies the file encoding. Defaults to ASCII. 139 | #> 140 | function Set-TargetResource 141 | { 142 | [CmdletBinding()] 143 | param 144 | ( 145 | [Parameter(Mandatory = $true)] 146 | [ValidateNotNullOrEmpty()] 147 | [System.String] 148 | $Path, 149 | 150 | [Parameter(Mandatory = $true)] 151 | [ValidateNotNullOrEmpty()] 152 | [System.String] 153 | $Name, 154 | 155 | [Parameter()] 156 | [ValidateSet('Present', 'Absent')] 157 | [System.String] 158 | $Ensure = 'Present', 159 | 160 | [Parameter()] 161 | [ValidateSet('Text', 'Secret')] 162 | [System.String] 163 | $Type = 'Text', 164 | 165 | [Parameter()] 166 | [System.String] 167 | $Text, 168 | 169 | [Parameter()] 170 | [System.Management.Automation.PSCredential] 171 | [System.Management.Automation.Credential()] 172 | $Secret, 173 | 174 | [Parameter()] 175 | [System.Boolean] 176 | $IgnoreNameCase = $false, 177 | 178 | [Parameter()] 179 | [System.Boolean] 180 | $IgnoreValueCase = $false, 181 | 182 | [Parameter()] 183 | [ValidateSet('ASCII', 'BigEndianUnicode', 'BigEndianUTF32', 'UTF8', 'UTF32')] 184 | [System.String] 185 | $Encoding 186 | ) 187 | 188 | Assert-ParametersValid @PSBoundParameters 189 | 190 | $fileContent = Get-Content -Path $Path -Raw -ErrorAction SilentlyContinue 191 | $fileEncoding = Get-FileEncoding -Path $Path -ErrorAction SilentlyContinue 192 | 193 | $fileProperties = @{ 194 | Path = $Path 195 | NoNewline = $true 196 | Force = $true 197 | } 198 | 199 | Write-Verbose -Message ($script:localizedData.SearchForKeyMessage -f $Path, $Name) 200 | 201 | if ($Type -eq 'Secret') 202 | { 203 | $Text = $Secret.GetNetworkCredential().Password 204 | } # if 205 | 206 | if ($null -ne $fileContent) 207 | { 208 | # Determine the EOL characters used in the file 209 | $eolChars = Get-TextEolCharacter -Text $fileContent 210 | 211 | # Setup the Regex Options that will be used 212 | $regExOptions = [System.Text.RegularExpressions.RegexOptions]::Multiline 213 | 214 | if ($IgnoreNameCase) 215 | { 216 | $regExOptions += [System.Text.RegularExpressions.RegexOptions]::IgnoreCase 217 | } 218 | 219 | # Search the key that matches the requested key 220 | $results = [regex]::Matches($fileContent, "^[\s]*$Name=([^\n\r]*)", $regExOptions) 221 | 222 | if ($Ensure -eq 'Present') 223 | { 224 | # The key value pair should exist 225 | $keyValuePair = '{0}={1}{2}' -f $Name, $Text, $eolChars 226 | 227 | if ($results.Count -eq 0) 228 | { 229 | # The key value pair was not found so add it to the end of the file 230 | if (-not $fileContent.EndsWith($eolChars)) 231 | { 232 | $fileContent += $eolChars 233 | } # if 234 | 235 | $fileContent += $keyValuePair 236 | 237 | Write-Verbose -Message ($script:localizedData.KeyAddMessage -f $Path, $Name) 238 | } 239 | else 240 | { 241 | # The key value pair was found so update it 242 | $fileContent = [regex]::Replace($fileContent, "^[\s]*$Name=(.*)($eolChars*)", $keyValuePair, $regExOptions) 243 | 244 | Write-Verbose -Message ($script:localizedData.KeyUpdateMessage -f $Path, $Name) 245 | } # if 246 | } 247 | else 248 | { 249 | if ($results.Count -eq 0) 250 | { 251 | if ($PSBoundParameters.ContainsKey('Encoding') -and ($Encoding -eq $fileEncoding)) 252 | { 253 | # The Key does not exists and should not, and encoding is in the desired state, so don't do anything 254 | return 255 | } 256 | else 257 | { 258 | Write-Verbose -Message ($script:localizedData.FileEncodingNotInDesiredState -f $fileEncoding, $Encoding) 259 | } 260 | } 261 | else 262 | { 263 | # The Key exists in the file but should not so remove it 264 | $fileContent = [regex]::Replace($fileContent, "^[\s]*$Name=(.*)$eolChars", '', $regExOptions) 265 | 266 | Write-Verbose -Message ($script:localizedData.KeyRemoveMessage -f $Path, $Name) 267 | } 268 | } # if 269 | } 270 | else 271 | { 272 | $fileContent = '{0}={1}' -f $Name, $Text 273 | } # if 274 | 275 | $fileProperties.Add('Value', $fileContent) 276 | 277 | # Verify encoding is not set to the passed parameter or the default of ASCII 278 | if ($PSBoundParameters.ContainsKey('Encoding') -and ($Encoding -ne ($fileEncoding -or 'ASCII'))) 279 | { 280 | # Add encoding parameter and value to the hashtable 281 | $fileProperties.Add('Encoding', $Encoding) 282 | } 283 | 284 | Set-Content @fileProperties 285 | } 286 | 287 | <# 288 | .SYNOPSIS 289 | Tests the current state of a key in a key value pair text file. 290 | 291 | .PARAMETER Path 292 | The path to the key value pair text file. 293 | 294 | .PARAMETER Name 295 | The name of the key. 296 | 297 | .PARAMETER Ensure 298 | Specifies the if the key value pair with the specified key should exist in the file. 299 | 300 | .PARAMETER Type 301 | Specifies the value type to use as the replacement string. Defaults to 'Text'. 302 | 303 | .PARAMETER Text 304 | The text to replace the value with in the identified key. 305 | Only used when Type is set to 'Text'. 306 | 307 | .PARAMETER Secret 308 | The secret text to replace the value with in the identified key. 309 | Only used when Type is set to 'Secret'. 310 | 311 | .PARAMETER IgnoreNameCase 312 | Ignore the case of the name of the key. Defaults to $False. 313 | 314 | .PARAMETER IgnoreValueCase 315 | Ignore the case of any text or secret when determining if it they need to be updated. 316 | Defaults to $False. 317 | 318 | .PARAMETER Encoding 319 | Specifies the file encoding. Defaults to ASCII. 320 | #> 321 | function Test-TargetResource 322 | { 323 | [OutputType([System.Boolean])] 324 | [CmdletBinding()] 325 | param 326 | ( 327 | [Parameter(Mandatory = $true)] 328 | [ValidateNotNullOrEmpty()] 329 | [System.String] 330 | $Path, 331 | 332 | [Parameter(Mandatory = $true)] 333 | [ValidateNotNullOrEmpty()] 334 | [System.String] 335 | $Name, 336 | 337 | [Parameter()] 338 | [ValidateSet('Present', 'Absent')] 339 | [System.String] 340 | $Ensure = 'Present', 341 | 342 | [Parameter()] 343 | [ValidateSet('Text', 'Secret')] 344 | [System.String] 345 | $Type = 'Text', 346 | 347 | [Parameter()] 348 | [System.String] 349 | $Text, 350 | 351 | [Parameter()] 352 | [System.Management.Automation.PSCredential] 353 | [System.Management.Automation.Credential()] 354 | $Secret, 355 | 356 | [Parameter()] 357 | [System.Boolean] 358 | $IgnoreNameCase = $false, 359 | 360 | [Parameter()] 361 | [System.Boolean] 362 | $IgnoreValueCase = $false, 363 | 364 | [Parameter()] 365 | [ValidateSet('ASCII', 'BigEndianUnicode', 'BigEndianUTF32', 'UTF8', 'UTF32')] 366 | [System.String] 367 | $Encoding 368 | ) 369 | 370 | Assert-ParametersValid @PSBoundParameters 371 | 372 | <# 373 | If the file being managed does not exist then return true if 374 | the key should be absent or false if it should be present. 375 | #> 376 | if (-not (Test-Path -Path $Path)) 377 | { 378 | Write-Verbose -Message ($script:localizedData.KeyValuePairFileNotFound -f $Path) 379 | 380 | return ($Ensure -eq 'Absent') 381 | } 382 | 383 | $fileContent = Get-Content -Path $Path -Raw 384 | 385 | if ($null -eq $fileContent) 386 | { 387 | Write-Verbose -Message ($script:localizedData.KeyValuePairFileIsEmpty -f $Path) 388 | 389 | return ($Ensure -eq 'Absent') 390 | } 391 | 392 | $desiredConfigurationMatch = $true 393 | $fileEncoding = Get-FileEncoding -Path $Path 394 | $regExOptions = [System.Text.RegularExpressions.RegexOptions]::Multiline 395 | 396 | Write-Verbose -Message ($script:localizedData.SearchForKeyMessage -f $Path, $Name) 397 | 398 | if ($IgnoreNameCase) 399 | { 400 | $regExOptions += [System.Text.RegularExpressions.RegexOptions]::IgnoreCase 401 | } 402 | 403 | # Search the key that matches the requested key 404 | $results = [regex]::Matches($fileContent, "^[\s]*$Name=([^\n\r]*)", $regExOptions) 405 | 406 | if ($results.Count -eq 0) 407 | { 408 | # No matches found 409 | if ($Ensure -eq 'Present') 410 | { 411 | # The key value pairs should exist but do not 412 | Write-Verbose -Message ($script:localizedData.KeyNotFoundButShouldExistMessage -f $Path, $Name) 413 | 414 | $desiredConfigurationMatch = $false 415 | } 416 | else 417 | { 418 | # The key value pairs should exist and do 419 | Write-Verbose -Message ($script:localizedData.KeyNotFoundAndShouldNotExistMessage -f $Path, $Name) 420 | } # if 421 | } 422 | else 423 | { 424 | # One or more key value pairs were found 425 | if ($Ensure -eq 'Present') 426 | { 427 | # The key value pairs should exist - but check values 428 | if ($Type -eq 'Secret') 429 | { 430 | $Text = $Secret.GetNetworkCredential().Password 431 | } # if 432 | 433 | # Check each found key value pair and check it has the correct value 434 | foreach ($match in $results) 435 | { 436 | if (($IgnoreValueCase -and ($match.Groups[1].Value -ne $Text)) -or ` 437 | (-not $IgnoreValueCase -and ($match.Groups[1].Value -cne $Text))) 438 | { 439 | $desiredConfigurationMatch = $false 440 | } # 441 | } # foreach 442 | 443 | if ($desiredConfigurationMatch) 444 | { 445 | Write-Verbose -Message ($script:localizedData.KeyFoundButNoReplacementMessage -f $Path, $Name) 446 | } 447 | } 448 | else 449 | { 450 | # The key value pairs should not exist 451 | Write-Verbose -Message ($script:localizedData.KeyFoundButShouldNotExistMessage -f $Path, $Name) 452 | 453 | $desiredConfigurationMatch = $false 454 | } # if 455 | } # if 456 | 457 | if ($PSBoundParameters.ContainsKey('Encoding') -and ($Encoding -ne $fileEncoding)) 458 | { 459 | # File encoding is not in desired state 460 | Write-Verbose -Message ($script:localizedData.FileEncodingNotInDesiredState -f $fileEncoding, $Encoding) 461 | 462 | $desiredConfigurationMatch = $false 463 | } 464 | 465 | return $desiredConfigurationMatch 466 | } 467 | 468 | <# 469 | .SYNOPSIS 470 | Validates the parameters that have been passed are valid. 471 | If they are not valid then an exception will be thrown. 472 | 473 | .PARAMETER Path 474 | The path to the key value pair text file. 475 | 476 | .PARAMETER Name 477 | The name of the key. 478 | 479 | .PARAMETER Ensure 480 | Specifies the if the key value pair with the specified key should exist in the file. 481 | 482 | .PARAMETER Type 483 | Specifies the value type to use as the replacement string. Defaults to 'Text'. 484 | 485 | .PARAMETER Text 486 | The text to replace the value with in the identified key. 487 | Only used when Type is set to 'Text'. 488 | 489 | .PARAMETER Secret 490 | The secret text to replace the value with in the identified key. 491 | Only used when Type is set to 'Secret'. 492 | 493 | .PARAMETER IgnoreNameCase 494 | Ignore the case of the name of the key. Defaults to $False. 495 | 496 | .PARAMETER IgnoreValueCase 497 | Ignore the case of any text or secret when determining if it they need to be updated. 498 | Defaults to $False. 499 | 500 | .PARAMETER Encoding 501 | Specifies the file encoding. Defaults to ASCII. 502 | #> 503 | function Assert-ParametersValid 504 | { 505 | [CmdletBinding()] 506 | param 507 | ( 508 | [Parameter(Mandatory = $true)] 509 | [ValidateNotNullOrEmpty()] 510 | [System.String] 511 | $Path, 512 | 513 | [Parameter(Mandatory = $true)] 514 | [ValidateNotNullOrEmpty()] 515 | [System.String] 516 | $Name, 517 | 518 | [Parameter()] 519 | [ValidateSet('Present', 'Absent')] 520 | [System.String] 521 | $Ensure = 'Present', 522 | 523 | [Parameter()] 524 | [ValidateSet('Text', 'Secret')] 525 | [System.String] 526 | $Type = 'Text', 527 | 528 | [Parameter()] 529 | [System.String] 530 | $Text, 531 | 532 | [Parameter()] 533 | [System.Management.Automation.PSCredential] 534 | [System.Management.Automation.Credential()] 535 | $Secret, 536 | 537 | [Parameter()] 538 | [System.Boolean] 539 | $IgnoreNameCase = $false, 540 | 541 | [Parameter()] 542 | [System.Boolean] 543 | $IgnoreValueCase = $false, 544 | 545 | [Parameter()] 546 | [ValidateSet('ASCII', 'BigEndianUnicode', 'BigEndianUTF32', 'UTF8', 'UTF32')] 547 | [System.String] 548 | $Encoding 549 | ) 550 | 551 | # Does the file's parent path exist? 552 | $parentPath = Split-Path -Path $Path -Parent 553 | 554 | if (-not (Test-Path -Path $parentPath)) 555 | { 556 | New-InvalidArgumentException ` 557 | -Message ($script:localizedData.FileParentNotFoundError -f $Path) ` 558 | -ArgumentName 'Path' 559 | } # if 560 | } 561 | 562 | Export-ModuleMember -Function *-TargetResource 563 | -------------------------------------------------------------------------------- /source/DSCResources/DSC_KeyValuePairFile/DSC_KeyValuePairFile.schema.mof: -------------------------------------------------------------------------------- 1 | [ClassVersion("1.0.0.0"), FriendlyName("KeyValuePairFile")] 2 | class DSC_KeyValuePairFile : OMI_BaseResource 3 | { 4 | [Key, Description("The path to the key value pair text file.")] String Path; 5 | [Key, Description("The name of the key.")] String Name; 6 | [Write, Description("Specifies the if the key value pair with the specified key should exist in the file."),ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] String Ensure; 7 | [Write, Description("Specifies the value type to use as the replacement string. Defaults to 'Text'."),ValueMap{"Text", "Secret"},Values{"Text", "Secret"}] String Type; 8 | [Write, Description("The text to replace the value with in the identified key. Only used when Type is set to 'Text'.")] String Text; 9 | [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; 10 | [Write, Description("Ignore the case of the name of the key. Defaults to $False.")] Boolean IgnoreNameCase; 11 | [Write, Description("Ignore the case of any text or secret when determining if it they need to be updated. Defaults to $False.")] Boolean IgnoreValueCase; 12 | [Write, Description("Specifies the file encoding. Defaults to ASCII"),ValueMap{"ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32"},Values{"ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32"}] String Encoding; 13 | }; 14 | -------------------------------------------------------------------------------- /source/DSCResources/DSC_KeyValuePairFile/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | The KeyValuePairFile resource is used to add, remove or set key/value pairs 4 | in a text file containing key/value pair entries. 5 | 6 | This resource is intended to be used to set key/value pair values in 7 | configuration or data files where no partitions or headings are used to 8 | separate entries and each line contains only a single entry. 9 | 10 | This resource should not be used to configure INI files. 11 | The [IniSettingFile](IniSettingFile.md) resource should be used instead. 12 | -------------------------------------------------------------------------------- /source/DSCResources/DSC_KeyValuePairFile/en-US/DSC_KeyValuePairFile.schema.mfl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsccommunity/FileContentDsc/42e61eb6b752cf747c6bb352357046bbb37d95f1/source/DSCResources/DSC_KeyValuePairFile/en-US/DSC_KeyValuePairFile.schema.mfl -------------------------------------------------------------------------------- /source/DSCResources/DSC_KeyValuePairFile/en-US/DSC_KeyValuePairFile.strings.psd1: -------------------------------------------------------------------------------- 1 | # Localized resources for DSC_ReplaceText 2 | 3 | ConvertFrom-StringData @' 4 | SearchForKeyMessage = Searching for key '{1}' in file '{0}'. 5 | KeyNotFoundMessage = Key '{1}' not found in file '{0}'. 6 | KeyFoundMessage = Key '{1}' found in file '{0}' with the value(s) '{2}'. 7 | KeyRemoveMessage = Key '{1}' found in file '{0}' but has been removed. 8 | KeyAddMessage = Key '{1}' not found in file '{0}' so has been added. 9 | KeyUpdateMessage = Key '{1}' found in file '{0}' and has been updated. 10 | KeyNotFoundButShouldExistMessage = Key '{1}' not found in file '{0}' but should exist. Change required. 11 | KeyNotFoundAndShouldNotExistMessage = Key '{1}' not found in file '{0}' and should not exist. Change not required. 12 | KeyFoundButNoReplacementMessage = Key '{1}' found in file '{0}' and should exist and value(s) are correct. Change not required. 13 | KeyFoundButShouldNotExistMessage = Key '{1}' found in file '{0}' but should not exist. Change required. 14 | KeyValuePairFileNotFound = Key Value Pair file '{0}' not found. 15 | KeyValuePairFileIsEmpty = Key Value Pair file '{0}' is empty. 16 | FileParentNotFoundError = File parent path '{0}' not found. 17 | FileEncodingNotInDesiredState = File encoding is set to '{0}' but should be set to '{1}', Change required. 18 | '@ 19 | -------------------------------------------------------------------------------- /source/DSCResources/DSC_ReplaceText/DSC_ReplaceText.psm1: -------------------------------------------------------------------------------- 1 | Set-StrictMode -Version 'Latest' 2 | 3 | $modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules' 4 | 5 | # Import the Networking Common Modules 6 | Import-Module -Name (Join-Path -Path $modulePath ` 7 | -ChildPath (Join-Path -Path 'FileContentDsc.Common' ` 8 | -ChildPath 'FileContentDsc.Common.psm1')) 9 | 10 | Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common') 11 | 12 | # Import Localization Strings 13 | $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' 14 | 15 | <# 16 | .SYNOPSIS 17 | Retrieves the current state of the text file. 18 | 19 | .PARAMETER Path 20 | The path to the text file to replace the string in. 21 | 22 | .PARAMETER Search 23 | The RegEx string to use to search the text file. 24 | #> 25 | function Get-TargetResource 26 | { 27 | [OutputType([Hashtable])] 28 | [CmdletBinding()] 29 | param 30 | ( 31 | [Parameter(Mandatory = $true)] 32 | [ValidateNotNullOrEmpty()] 33 | [System.String] 34 | $Path, 35 | 36 | [Parameter(Mandatory = $true)] 37 | [ValidateNotNullOrEmpty()] 38 | [System.String] 39 | $Search 40 | ) 41 | 42 | Assert-ParametersValid @PSBoundParameters 43 | 44 | $fileContent = Get-Content -Path $Path -Raw 45 | $fileEncoding = Get-FileEncoding $Path 46 | 47 | Write-Verbose -Message ($script:localizedData.SearchForTextMessage -f ` 48 | $Path, $Search) 49 | 50 | $text = '' 51 | 52 | # Search the file content for any matches 53 | $results = [regex]::Matches($fileContent, $Search) 54 | 55 | if ($results.Count -eq 0) 56 | { 57 | # No matches found - already in state 58 | Write-Verbose -Message ($script:localizedData.StringNotFoundMessage -f ` 59 | $Path, $Search) 60 | } 61 | else 62 | { 63 | $text = ($results.Value -join ',') 64 | 65 | Write-Verbose -Message ($script:localizedData.StringMatchFoundMessage -f ` 66 | $Path, $Search, $text) 67 | } # if 68 | 69 | return @{ 70 | Path = $Path 71 | Search = $Search 72 | Type = 'Text' 73 | Text = $text 74 | Encoding = $fileEncoding 75 | } 76 | } 77 | 78 | <# 79 | .SYNOPSIS 80 | Replaces text the matches the RegEx in the file. 81 | 82 | .PARAMETER Path 83 | The path to the text file to replace the string in. 84 | 85 | .PARAMETER Search 86 | The RegEx string to use to search the text file. 87 | 88 | .PARAMETER Type 89 | Specifies the value type to use as the replacement string. Defaults to 'Text'. 90 | 91 | .PARAMETER Text 92 | The text to replace the text identifed by the RegEx. 93 | Only used when Type is set to 'Text'. 94 | 95 | .PARAMETER Secret 96 | The secret text to replace the text identified by the RegEx. 97 | Only used when Type is set to 'Secret'. 98 | 99 | .PARAMETER AllowAppend 100 | Specifies to append text to the file being modified. Adds the ability to add a configuration entry. 101 | 102 | .PARAMETER Encoding 103 | Specifies the file encoding. Defaults to ASCII. 104 | #> 105 | function Set-TargetResource 106 | { 107 | [CmdletBinding()] 108 | param 109 | ( 110 | [Parameter(Mandatory = $true)] 111 | [ValidateNotNullOrEmpty()] 112 | [System.String] 113 | $Path, 114 | 115 | [Parameter(Mandatory = $true)] 116 | [ValidateNotNullOrEmpty()] 117 | [System.String] 118 | $Search, 119 | 120 | [Parameter()] 121 | [ValidateSet('Text', 'Secret')] 122 | [System.String] 123 | $Type = 'Text', 124 | 125 | [Parameter()] 126 | [System.String] 127 | $Text, 128 | 129 | [Parameter()] 130 | [System.Management.Automation.PSCredential] 131 | [System.Management.Automation.Credential()] 132 | $Secret, 133 | 134 | [Parameter()] 135 | [System.Boolean] 136 | $AllowAppend = $false, 137 | 138 | [Parameter()] 139 | [ValidateSet("ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32")] 140 | [System.String] 141 | $Encoding 142 | ) 143 | 144 | Assert-ParametersValid @PSBoundParameters 145 | 146 | $fileContent = Get-Content -Path $Path -Raw -ErrorAction SilentlyContinue 147 | $fileEncoding = Get-FileEncoding $Path 148 | 149 | $fileProperties = @{ 150 | Path = $Path 151 | NoNewline = $true 152 | Force = $true 153 | } 154 | 155 | if ($Type -eq 'Secret') 156 | { 157 | Write-Verbose -Message ($script:localizedData.StringReplaceSecretMessage -f ` 158 | $Path) 159 | 160 | $Text = $Secret.GetNetworkCredential().Password 161 | } 162 | elseif ($PSBoundParameters.ContainsKey('Encoding')) 163 | { 164 | if ($Encoding -eq $fileEncoding) 165 | { 166 | Write-Verbose -Message ($script:localizedData.StringReplaceTextMessage -f ` 167 | $Path, $Text) 168 | } 169 | else 170 | { 171 | Write-Verbose -Message ($script:localizedData.StringReplaceTextMessage -f ` 172 | $Path, $Text) 173 | 174 | # Add encoding parameter and value to the hashtable 175 | $fileProperties.Add('Encoding', $Encoding) 176 | } 177 | } 178 | 179 | if ($null -eq $fileContent) 180 | { 181 | # Configuration file does not exist 182 | $fileContent = $Text 183 | } 184 | elseif ([regex]::Matches($fileContent, $Search).Count -eq 0 -and $AllowAppend -eq $true) 185 | { 186 | # Configuration file exists but Text does not exist so lets add it 187 | $fileContent = Add-ConfigurationEntry -FileContent $fileContent -Text $Text 188 | } 189 | else 190 | { 191 | # Configuration file exists but Text not in a desired state so lets update it 192 | $fileContent = $fileContent -Replace $Search, $Text 193 | } 194 | 195 | $fileProperties.Add('Value', $fileContent) 196 | 197 | Set-Content @fileProperties 198 | } 199 | 200 | <# 201 | .SYNOPSIS 202 | Tests if any text in the file matches the RegEx. 203 | 204 | .PARAMETER Path 205 | The path to the text file to replace the string in. 206 | 207 | .PARAMETER Search 208 | The RegEx string to use to search the text file. 209 | 210 | .PARAMETER Type 211 | Specifies the value type to use as the replacement string. Defaults to 'Text'. 212 | 213 | .PARAMETER Text 214 | The text to replace the text identifed by the RegEx. 215 | Only used when Type is set to 'Text'. 216 | 217 | .PARAMETER Secret 218 | The secret text to replace the text identified by the RegEx. 219 | Only used when Type is set to 'Secret'. 220 | 221 | .PARAMETER AllowAppend 222 | Specifies to append text to the file being modified. Adds the ability to add a configuration entry. 223 | 224 | .PARAMETER Encoding 225 | Specifies the file encoding. Defaults to ASCII. 226 | #> 227 | function Test-TargetResource 228 | { 229 | [OutputType([System.Boolean])] 230 | [CmdletBinding()] 231 | param 232 | ( 233 | [Parameter(Mandatory = $true)] 234 | [ValidateNotNullOrEmpty()] 235 | [System.String] 236 | $Path, 237 | 238 | [Parameter(Mandatory = $true)] 239 | [ValidateNotNullOrEmpty()] 240 | [System.String] 241 | $Search, 242 | 243 | [Parameter()] 244 | [ValidateSet('Text', 'Secret')] 245 | [System.String] 246 | $Type = 'Text', 247 | 248 | [Parameter()] 249 | [System.String] 250 | $Text, 251 | 252 | [Parameter()] 253 | [System.Management.Automation.PSCredential] 254 | [System.Management.Automation.Credential()] 255 | $Secret, 256 | 257 | [Parameter()] 258 | [System.Boolean] 259 | $AllowAppend = $false, 260 | 261 | [Parameter()] 262 | [ValidateSet("ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32")] 263 | [System.String] 264 | $Encoding 265 | ) 266 | 267 | Assert-ParametersValid @PSBoundParameters 268 | 269 | # Check if file being managed exists. If not return $false. 270 | if (-not (Test-Path -Path $Path)) 271 | { 272 | return $false 273 | } 274 | 275 | $fileContent = Get-Content -Path $Path -Raw 276 | $fileEncoding = Get-FileEncoding $Path 277 | 278 | Write-Verbose -Message ($script:localizedData.SearchForTextMessage -f ` 279 | $Path, $Search) 280 | 281 | # Search the file content for any matches 282 | $results = [regex]::Matches($fileContent, $Search) 283 | 284 | if ($results.Count -eq 0) 285 | { 286 | if ($AllowAppend -eq $true) 287 | { 288 | # No matches found - but we want to append 289 | Write-Verbose -Message ($script:localizedData.StringNotFoundMessageAppend -f ` 290 | $Path, $Search) 291 | 292 | return $false 293 | } 294 | if ($PSBoundParameters.ContainsKey('Encoding')) 295 | { 296 | if ($Encoding -eq $fileEncoding) 297 | { 298 | # No matches found and encoding is in desired state 299 | Write-Verbose -Message ($script:localizedData.StringNotFoundMessage -f ` 300 | $Path, $Search) 301 | 302 | return $true 303 | } 304 | else 305 | { 306 | # No matches found but encoding is not in desired state 307 | Write-Verbose -Message ($script:localizedData.FileEncodingNotInDesiredState -f ` 308 | $fileEncoding, $Encoding) 309 | 310 | return $false 311 | } 312 | } 313 | } 314 | 315 | # Flag to signal whether settings are correct 316 | [System.Boolean] $desiredConfigurationMatch = $true 317 | 318 | if ($Type -eq 'Secret') 319 | { 320 | $Text = $Secret.GetNetworkCredential().Password 321 | } # if 322 | 323 | foreach ($result in $results) 324 | { 325 | if ($result.Value -ne $Text) 326 | { 327 | $desiredConfigurationMatch = $false 328 | } # if 329 | } # foreach 330 | 331 | if ($desiredConfigurationMatch) 332 | { 333 | Write-Verbose -Message ($script:localizedData.StringNoReplacementMessage -f ` 334 | $Path, $Search) 335 | } 336 | else 337 | { 338 | Write-Verbose -Message ($script:localizedData.StringReplacementRequiredMessage -f ` 339 | $Path, $Search) 340 | } # if 341 | 342 | return $desiredConfigurationMatch 343 | } 344 | 345 | <# 346 | .SYNOPSIS 347 | Validates the parameters that have been passed are valid. 348 | If they are not valid then an exception will be thrown. 349 | 350 | .PARAMETER Path 351 | The path to the text file to replace the string in. 352 | 353 | .PARAMETER Search 354 | The RegEx string to use to search the text file. 355 | 356 | .PARAMETER Type 357 | Specifies the value type to use as the replacement string. Defaults to 'Text'. 358 | 359 | .PARAMETER Text 360 | The text to replace the text identifed by the RegEx. 361 | Only used when Type is set to 'Text'. 362 | 363 | .PARAMETER Secret 364 | The secret text to replace the text identified by the RegEx. 365 | Only used when Type is set to 'Secret'. 366 | 367 | .PARAMETER Encoding 368 | Specifies the file encoding. Defaults to ASCII. 369 | #> 370 | function Assert-ParametersValid 371 | { 372 | [CmdletBinding()] 373 | param 374 | ( 375 | [Parameter(Mandatory = $true)] 376 | [ValidateNotNullOrEmpty()] 377 | [System.String] 378 | $Path, 379 | 380 | [Parameter(Mandatory = $true)] 381 | [ValidateNotNullOrEmpty()] 382 | [System.String] 383 | $Search, 384 | 385 | [Parameter()] 386 | [ValidateSet('Text', 'Secret')] 387 | [System.String] 388 | $Type = 'Text', 389 | 390 | [Parameter()] 391 | [System.String] 392 | $Text, 393 | 394 | [Parameter()] 395 | [System.Management.Automation.PSCredential] 396 | [System.Management.Automation.Credential()] 397 | $Secret, 398 | 399 | [Parameter()] 400 | [System.Boolean] 401 | $AllowAppend = $false, 402 | 403 | [Parameter()] 404 | [ValidateSet("ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32")] 405 | [System.String] 406 | $Encoding 407 | ) 408 | 409 | # Does the file's parent path exist? 410 | $parentPath = Split-Path -Path $Path -Parent 411 | if (-not (Test-Path -Path $parentPath)) 412 | { 413 | New-InvalidArgumentException ` 414 | -Message ($script:localizedData.FileParentNotFoundError -f $parentPath) ` 415 | -ArgumentName 'Path' 416 | } # if 417 | } 418 | 419 | <# 420 | .SYNOPSIS 421 | Uses the stringBuilder class to append a configuration entry to the existing file content. 422 | 423 | .PARAMETER FileContent 424 | The existing file content of the configuration file. 425 | 426 | .PARAMETER Text 427 | The text to append to the end of the FileContent. 428 | #> 429 | function Add-ConfigurationEntry 430 | { 431 | [OutputType([System.String])] 432 | [CmdletBinding()] 433 | param 434 | ( 435 | [Parameter(Mandatory = $true)] 436 | [System.String] 437 | $FileContent, 438 | 439 | [Parameter(Mandatory = $true)] 440 | [System.String] 441 | $Text 442 | ) 443 | 444 | if ($FileContent -match '\n$' -and $FileContent -notmatch '\r\n$') 445 | { 446 | # default *nix line ending 447 | $detectedNewLineFormat = "`n" 448 | } 449 | else 450 | { 451 | # default Windows line ending 452 | $detectedNewLineFormat = "`r`n" 453 | } 454 | 455 | $stringBuilder = New-Object -TypeName System.Text.StringBuilder 456 | 457 | $null = $stringBuilder.Append($FileContent) 458 | $null = $stringBuilder.Append($Text) 459 | $null = $stringBuilder.Append($detectedNewLineFormat) 460 | 461 | return $stringBuilder.ToString() 462 | } 463 | 464 | Export-ModuleMember -Function *-TargetResource 465 | -------------------------------------------------------------------------------- /source/DSCResources/DSC_ReplaceText/DSC_ReplaceText.schema.mof: -------------------------------------------------------------------------------- 1 | [ClassVersion("1.0.0.0"), FriendlyName("ReplaceText")] 2 | class DSC_ReplaceText : OMI_BaseResource 3 | { 4 | [Key, Description("The path to the text file to replace the string in.")] String Path; 5 | [Key, Description("The RegEx string to use to search the text file.")] String Search; 6 | [Write, Description("Specifies the value type to use as the replacement string. Defaults to 'Text'."),ValueMap{"Text", "Secret"},Values{"Text", "Secret"}] String Type; 7 | [Write, Description("The text to replace the text identified by the RegEx. Only used when Type is set to 'Text'.")] String Text; 8 | [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; 9 | [Write, Description("Specifies to append text to the file being modified. Adds the ability to add a configuration entry.")] Boolean AllowAppend; 10 | [Write, Description("Specifies the file encoding. Defaults to ASCII"),ValueMap{"ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32"},Values{"ASCII", "BigEndianUnicode", "BigEndianUTF32", "UTF8", "UTF32"}] String Encoding; 11 | }; 12 | -------------------------------------------------------------------------------- /source/DSCResources/DSC_ReplaceText/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | The resource is used to replace strings matching a regular expression in a 4 | text file. 5 | 6 | It can be used to replace strings matched with a regular expression with 7 | either a text string or a secret which is provided in the password of a 8 | credential object. 9 | -------------------------------------------------------------------------------- /source/DSCResources/DSC_ReplaceText/en-US/DSC_ReplaceText.schema.mfl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsccommunity/FileContentDsc/42e61eb6b752cf747c6bb352357046bbb37d95f1/source/DSCResources/DSC_ReplaceText/en-US/DSC_ReplaceText.schema.mfl -------------------------------------------------------------------------------- /source/DSCResources/DSC_ReplaceText/en-US/DSC_ReplaceText.strings.psd1: -------------------------------------------------------------------------------- 1 | # Localized resources for DSC_ReplaceText 2 | 3 | ConvertFrom-StringData @' 4 | SearchForTextMessage = Searching using RegEx '{1}' in file '{0}'. 5 | StringNotFoundMessageAppend = String not found using RegEx '{1}' in file '{0}', change required. 6 | StringNotFoundMessage = String not found using RegEx '{1}' in file '{0}', change not required. 7 | StringMatchFoundMessage = String(s) '{2}' found using RegEx '{1}' in file '{0}'. 8 | StringReplacementRequiredMessage = String found using RegEx '{1}' in file '{0}', replacement required. 9 | StringNoReplacementMessage = String found using RegEx '{1}' in file '{0}', no replacement required. 10 | StringReplaceTextMessage = String replaced by '{1}' in file '{0}'. 11 | StringReplaceSecretMessage = String replaced by secret text in file '{0}'. 12 | FileParentNotFoundError = File parent path '{0}' not found. 13 | FileEncodingNotInDesiredState = File encoding is set to '{0}' but should be set to '{1}', Change required. 14 | '@ 15 | -------------------------------------------------------------------------------- /source/Examples/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Examples 3 | 4 | This will help to understand how to setup certain scenarios with FileContentDsc 5 | resource module. 6 | 7 | ## Resource examples 8 | 9 | These are the links to the examples for each individual resource. 10 | 11 | - [IniSettingsFile](Resources/IniSettingsFile) 12 | - [KeyValuePairFile](Resources/KeyValuePairFile) 13 | - [ReplaceText](Resources/ReplaceText) 14 | -------------------------------------------------------------------------------- /source/Examples/Resources/IniSettingsFile/1-IniSettingsFile_SetPlainTextEntry_Config.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | .VERSION 1.0.0 3 | .GUID 389e1516-5961-4b13-b698-62fbfb8c1107 4 | .AUTHOR DSC Community 5 | .COMPANYNAME DSC Community 6 | .COPYRIGHT Copyright the DSC Community contributors. All rights reserved. 7 | .TAGS DSCConfiguration 8 | .LICENSEURI https://github.com/dsccommunity/FileContentDsc/blob/main/LICENSE 9 | .PROJECTURI https://github.com/dsccommunity/FileContentDsc 10 | .ICONURI 11 | .EXTERNALMODULEDEPENDENCIES 12 | .REQUIREDSCRIPTS 13 | .EXTERNALSCRIPTDEPENDENCIES 14 | .RELEASENOTES First version. 15 | .PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core 16 | #> 17 | 18 | #Requires -module FileContentDsc 19 | 20 | <# 21 | .DESCRIPTION 22 | Set the `Level` entry in the [Logging] section to `Information` 23 | in the file `c:\myapp\myapp.ini`. 24 | #> 25 | Configuration IniSettingsFile_SetPlainTextEntry_Config 26 | { 27 | Import-DSCResource -ModuleName FileContentDsc 28 | 29 | Node localhost 30 | { 31 | IniSettingsFile SetLogging 32 | { 33 | Path = 'c:\myapp\myapp.ini' 34 | Section = 'Logging' 35 | Key = 'Level' 36 | Text = 'Information' 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/Examples/Resources/IniSettingsFile/2-IniSettingsFile_SetSecretTextEntry_Config.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | .VERSION 1.0.0 3 | .GUID e1cbce56-1760-4208-b2dd-45cea4e87ab7 4 | .AUTHOR DSC Community 5 | .COMPANYNAME DSC Community 6 | .COPYRIGHT Copyright the DSC Community contributors. All rights reserved. 7 | .TAGS DSCConfiguration 8 | .LICENSEURI https://github.com/dsccommunity/FileContentDsc/blob/main/LICENSE 9 | .PROJECTURI https://github.com/dsccommunity/FileContentDsc 10 | .ICONURI 11 | .EXTERNALMODULEDEPENDENCIES 12 | .REQUIREDSCRIPTS 13 | .EXTERNALSCRIPTDEPENDENCIES 14 | .RELEASENOTES First version. 15 | .PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core 16 | #> 17 | 18 | #Requires -module FileContentDsc 19 | 20 | <# 21 | .DESCRIPTION 22 | Set the `ConnectionString` entry in the [Database] section to the password 23 | provided in the $Secret credential object in the file `c:\myapp\myapp.ini`. 24 | #> 25 | Configuration IniSettingsFile_SetSecretTextEntry_Config 26 | { 27 | param 28 | ( 29 | [Parameter()] 30 | [ValidateNotNullorEmpty()] 31 | [PSCredential] 32 | $Secret 33 | ) 34 | 35 | Import-DSCResource -ModuleName FileContentDsc 36 | 37 | Node localhost 38 | { 39 | IniSettingsFile SetConnectionString 40 | { 41 | Path = 'c:\myapp\myapp.ini' 42 | Section = 'Database' 43 | Key = 'ConnectionString' 44 | Type = 'Secret' 45 | Secret = $Secret 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/Examples/Resources/KeyValuePairFile/1-KeyValuePairFile_RemovePlainTextPair_Config.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | .VERSION 1.0.0 3 | .GUID d326f0fb-b169-4602-a508-dbcb07d0e883 4 | .AUTHOR DSC Community 5 | .COMPANYNAME DSC Community 6 | .COPYRIGHT Copyright the DSC Community contributors. All rights reserved. 7 | .TAGS DSCConfiguration 8 | .LICENSEURI https://github.com/dsccommunity/FileContentDsc/blob/main/LICENSE 9 | .PROJECTURI https://github.com/dsccommunity/FileContentDsc 10 | .ICONURI 11 | .EXTERNALMODULEDEPENDENCIES 12 | .REQUIREDSCRIPTS 13 | .EXTERNALSCRIPTDEPENDENCIES 14 | .RELEASENOTES First version. 15 | .PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core 16 | #> 17 | 18 | #Requires -module FileContentDsc 19 | 20 | <# 21 | .DESCRIPTION 22 | Remove all `Core.Logging` keys in the file `c:\myapp\myapp.conf`. 23 | #> 24 | Configuration KeyValuePairFile_RemovePlainTextPair_Config 25 | { 26 | Import-DSCResource -ModuleName FileContentDsc 27 | 28 | Node localhost 29 | { 30 | KeyValuePairFile RemoveCoreLogging 31 | { 32 | Path = 'c:\myapp\myapp.conf' 33 | Name = 'Core.Logging' 34 | Ensure = 'Absent' 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /source/Examples/Resources/KeyValuePairFile/2-KeyValuePairFile_SetPlainTextPair_Config.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | .VERSION 1.0.0 3 | .GUID 81ab6eb0-3052-46cd-bea5-653e89d38972 4 | .AUTHOR DSC Community 5 | .COMPANYNAME DSC Community 6 | .COPYRIGHT Copyright the DSC Community contributors. All rights reserved. 7 | .TAGS DSCConfiguration 8 | .LICENSEURI https://github.com/dsccommunity/FileContentDsc/blob/main/LICENSE 9 | .PROJECTURI https://github.com/dsccommunity/FileContentDsc 10 | .ICONURI 11 | .EXTERNALMODULEDEPENDENCIES 12 | .REQUIREDSCRIPTS 13 | .EXTERNALSCRIPTDEPENDENCIES 14 | .RELEASENOTES First version. 15 | .PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core 16 | #> 17 | 18 | #Requires -module FileContentDsc 19 | 20 | <# 21 | .DESCRIPTION 22 | Set all `Core.Logging` keys to `Information` or add it 23 | if it is missing in the file `c:\myapp\myapp.conf`. 24 | #> 25 | Configuration KeyValuePairFile_SetPlainTextPair_Config 26 | { 27 | Import-DSCResource -ModuleName FileContentDsc 28 | 29 | Node localhost 30 | { 31 | KeyValuePairFile SetCoreLogging 32 | { 33 | Path = 'c:\myapp\myapp.conf' 34 | Name = 'Core.Logging' 35 | Ensure = 'Present' 36 | Text = 'Information' 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/Examples/Resources/KeyValuePairFile/3-KeyValuePairFile_SetSecretTextPair_Config.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | .VERSION 1.0.0 3 | .GUID 89fa6d5d-c121-4502-8bac-5d3ea66e8e80 4 | .AUTHOR DSC Community 5 | .COMPANYNAME DSC Community 6 | .COPYRIGHT Copyright the DSC Community contributors. All rights reserved. 7 | .TAGS DSCConfiguration 8 | .LICENSEURI https://github.com/dsccommunity/FileContentDsc/blob/main/LICENSE 9 | .PROJECTURI https://github.com/dsccommunity/FileContentDsc 10 | .ICONURI 11 | .EXTERNALMODULEDEPENDENCIES 12 | .REQUIREDSCRIPTS 13 | .EXTERNALSCRIPTDEPENDENCIES 14 | .RELEASENOTES First version. 15 | .PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core 16 | #> 17 | 18 | #Requires -module FileContentDsc 19 | 20 | <# 21 | .DESCRIPTION 22 | Set all `Core.Password` keys to the password provided in the $Secret 23 | credential object or add it if it is missing in the file `c:\myapp\myapp.conf`. 24 | #> 25 | Configuration KeyValuePairFile_SetSecretTextPair_Config 26 | { 27 | param 28 | ( 29 | [Parameter()] 30 | [ValidateNotNullorEmpty()] 31 | [PSCredential] 32 | $Secret 33 | ) 34 | 35 | Import-DSCResource -ModuleName FileContentDsc 36 | 37 | Node localhost 38 | { 39 | KeyValuePairFile SetCorePassword 40 | { 41 | Path = 'c:\myapp\myapp.conf' 42 | Name = 'Core.Password' 43 | Ensure = 'Present' 44 | Type = 'Secret' 45 | Secret = $Secret 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/Examples/Resources/ReplaceText/1-ReplaceText_ReplacePlainSecretText_Config.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | .VERSION 1.0.0 3 | .GUID 6a6a7523-91c3-4038-b7f1-178b8dd6803d 4 | .AUTHOR DSC Community 5 | .COMPANYNAME DSC Community 6 | .COPYRIGHT Copyright the DSC Community contributors. All rights reserved. 7 | .TAGS DSCConfiguration 8 | .LICENSEURI https://github.com/dsccommunity/FileContentDsc/blob/main/LICENSE 9 | .PROJECTURI https://github.com/dsccommunity/FileContentDsc 10 | .ICONURI 11 | .EXTERNALMODULEDEPENDENCIES 12 | .REQUIREDSCRIPTS 13 | .EXTERNALSCRIPTDEPENDENCIES 14 | .RELEASENOTES First version. 15 | .PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core 16 | #> 17 | 18 | #Requires -module FileContentDsc 19 | 20 | <# 21 | .DESCRIPTION 22 | Set all occrurances of the string `%secret%` to be the value in 23 | the password set in the parameter $Secret PSCredential object 24 | in the file `c:\inetpub\wwwroot\default.htm`. 25 | #> 26 | Configuration ReplaceText_ReplacePlainSecretText_Config 27 | { 28 | param 29 | ( 30 | [Parameter()] 31 | [ValidateNotNullorEmpty()] 32 | [PSCredential] 33 | $Secret 34 | ) 35 | 36 | Import-DSCResource -ModuleName FileContentDsc 37 | 38 | Node localhost 39 | { 40 | ReplaceText SetSecretText 41 | { 42 | Path = 'c:\inetpub\wwwroot\default.htm' 43 | Search = '%secret%' 44 | Type = 'Secret' 45 | Secret = $Secret 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/Examples/Resources/ReplaceText/2-ReplaceText_ReplacePlainText_Config.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | .VERSION 1.0.0 3 | .GUID 40050783-8d84-4d71-be0c-a03c8da76133 4 | .AUTHOR DSC Community 5 | .COMPANYNAME DSC Community 6 | .COPYRIGHT Copyright the DSC Community contributors. All rights reserved. 7 | .TAGS DSCConfiguration 8 | .LICENSEURI https://github.com/dsccommunity/FileContentDsc/blob/main/LICENSE 9 | .PROJECTURI https://github.com/dsccommunity/FileContentDsc 10 | .ICONURI 11 | .EXTERNALMODULEDEPENDENCIES 12 | .REQUIREDSCRIPTS 13 | .EXTERNALSCRIPTDEPENDENCIES 14 | .RELEASENOTES First version. 15 | .PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core 16 | #> 17 | 18 | #Requires -module FileContentDsc 19 | 20 | <# 21 | .DESCRIPTION 22 | Set all occrurances of the string `%appname%` to be Awesome App` 23 | in the file `c:\inetpub\wwwroot\default.htm`. 24 | #> 25 | Configuration ReplaceText_ReplacePlainText_Config 26 | { 27 | Import-DSCResource -ModuleName FileContentDsc 28 | 29 | Node localhost 30 | { 31 | ReplaceText SetText 32 | { 33 | Path = 'c:\inetpub\wwwroot\default.htm' 34 | Search = '%appname%' 35 | Type = 'Text' 36 | Text = 'Awesome App' 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/Examples/Resources/ReplaceText/3-ReplaceText_ReplaceRegexText_Config.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | .VERSION 1.0.0 3 | .GUID b29bfcd1-95e1-47e1-971c-a2fffb223113 4 | .AUTHOR DSC Community 5 | .COMPANYNAME DSC Community 6 | .COPYRIGHT Copyright the DSC Community contributors. All rights reserved. 7 | .TAGS DSCConfiguration 8 | .LICENSEURI https://github.com/dsccommunity/FileContentDsc/blob/main/LICENSE 9 | .PROJECTURI https://github.com/dsccommunity/FileContentDsc 10 | .ICONURI 11 | .EXTERNALMODULEDEPENDENCIES 12 | .REQUIREDSCRIPTS 13 | .EXTERNALSCRIPTDEPENDENCIES 14 | .RELEASENOTES First version. 15 | .PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core 16 | #> 17 | 18 | #Requires -module FileContentDsc 19 | 20 | <# 21 | .DESCRIPTION 22 | Set all occrurances of a string matching the regular expression 23 | `` with the text `` 24 | in the file `c:\inetpub\wwwroot\default.htm` 25 | #> 26 | Configuration ReplaceText_ReplaceRegexText_Config 27 | { 28 | Import-DSCResource -ModuleName FileContentDsc 29 | 30 | Node localhost 31 | { 32 | ReplaceText SetTextWithRegex 33 | { 34 | Path = 'c:\inetpub\wwwroot\default.htm' 35 | Search = "" 36 | Type = 'Text' 37 | Text = '' 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /source/FileContentDsc.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | # Version number of this module. 3 | ModuleVersion = '0.0.1' 4 | 5 | # ID used to uniquely identify this module 6 | GUID = '6c9fe2f4-8af9-4bad-bd95-5909188c0f0a' 7 | 8 | # Author of this module 9 | Author = 'DSC Community' 10 | 11 | # Company or vendor of this module 12 | CompanyName = 'DSC Community' 13 | 14 | # Copyright statement for this module 15 | Copyright = 'Copyright the DSC Community contributors. All rights reserved.' 16 | 17 | # Description of the functionality provided by this module 18 | Description = 'DSC resources for for manipulating the content of text files.' 19 | 20 | # Minimum version of the Windows PowerShell engine required by this module 21 | PowerShellVersion = '4.0' 22 | 23 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 24 | CLRVersion = '4.0' 25 | 26 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 27 | FunctionsToExport = @() 28 | 29 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 30 | CmdletsToExport = @() 31 | 32 | # Variables to export from this module 33 | VariablesToExport = @() 34 | 35 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 36 | AliasesToExport = @() 37 | 38 | # DSC resources to export from this module 39 | DscResourcesToExport = @( 40 | 'ReplaceText', 41 | 'KeyValuePairFile', 42 | 'IniSettingsFile' 43 | ) 44 | 45 | # 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. 46 | PrivateData = @{ 47 | PSData = @{ 48 | # Set to a prerelease string value if the release should be a prerelease. 49 | Prerelease = '' 50 | 51 | # Tags applied to this module. These help with module discovery in online galleries. 52 | Tags = @('DesiredStateConfiguration', 'DSC', 'DSCResource', 'FileContent') 53 | 54 | # A URL to the license for this module. 55 | LicenseUri = 'https://github.com/dsccommunity/FileContentDsc/blob/main/LICENSE' 56 | 57 | # A URL to the main website for this project. 58 | ProjectUri = 'https://github.com/dsccommunity/FileContentDsc' 59 | 60 | # A URL to an icon representing this module. 61 | IconUri = 'https://dsccommunity.org/images/DSC_Logo_300p.png' 62 | 63 | # ReleaseNotes of this module 64 | ReleaseNotes = '' 65 | } # End of PSData hashtable 66 | } # End of PrivateData hashtable 67 | } 68 | -------------------------------------------------------------------------------- /source/Modules/FileContentDsc.Common/FileContentDsc.Common.psm1: -------------------------------------------------------------------------------- 1 | $modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules' 2 | Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common') 3 | 4 | # Import Localization Strings 5 | $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' 6 | 7 | # Add the types for reading/writing INI files 8 | Add-Type -TypeDefinition @" 9 | using System.IO; 10 | using System.Runtime.InteropServices; 11 | using System.Text; 12 | public static class IniFile 13 | { 14 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 15 | [return: MarshalAs(UnmanagedType.Bool)] 16 | private static extern bool WritePrivateProfileString(string lpAppName, 17 | string lpKeyName, string lpString, string lpFileName); 18 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] 19 | static extern uint GetPrivateProfileString( 20 | string lpAppName, 21 | string lpKeyName, 22 | string lpDefault, 23 | StringBuilder lpReturnedString, 24 | uint nSize, 25 | string lpFileName); 26 | public static void WriteIniSetting(string filePath, string section, string key, string value) 27 | { 28 | string fullPath = Path.GetFullPath(filePath); 29 | bool result = WritePrivateProfileString(section, key, value, fullPath); 30 | } 31 | public static string GetIniSetting(string filePath, string section, string key, string defaultValue) 32 | { 33 | string fullPath = Path.GetFullPath(filePath); 34 | StringBuilder sb = new StringBuilder(500); 35 | GetPrivateProfileString(section, key, defaultValue, sb, (uint)sb.Capacity, fullPath); 36 | return sb.ToString(); 37 | } 38 | } 39 | "@ 40 | 41 | <# 42 | .SYNOPSIS 43 | Determines the EOL characters used in a text string. 44 | If non EOL characters found at all then CRLF will be 45 | returned. 46 | 47 | .PARAMETER Text 48 | The text to determine the EOL from. 49 | #> 50 | function Get-TextEolCharacter 51 | { 52 | [CmdletBinding()] 53 | [OutputType([System.String])] 54 | param 55 | ( 56 | [Parameter(Mandatory = $true)] 57 | [System.String] 58 | $Text 59 | ) 60 | 61 | $eolChar = "`r`n" 62 | 63 | if (-not $Text.Contains("`r`n") -and $Text.Contains("`r")) 64 | { 65 | $eolChar = "`r" 66 | } # if 67 | 68 | return $eolChar 69 | } 70 | 71 | <# 72 | .SYNOPSIS 73 | Sets or adds the value of an entry in an INI file. 74 | 75 | .PARAMETER Path 76 | The path to the INI file to set the value in. 77 | 78 | .PARAMETER Section 79 | The section to add/set the entry in. 80 | 81 | .PARAMETER Key 82 | The name of the entry to add/set the value to. 83 | 84 | .PARAMETER Value 85 | The value to set the entry to. 86 | #> 87 | function Set-IniSettingFileValue 88 | { 89 | [CmdletBinding()] 90 | param 91 | ( 92 | [Parameter(Mandatory = $true)] 93 | [System.String] 94 | $Path, 95 | 96 | [Parameter(Mandatory = $true)] 97 | [System.String] 98 | $Section, 99 | 100 | [Parameter(Mandatory = $true)] 101 | [System.String] 102 | $Key, 103 | 104 | [Parameter(Mandatory = $true)] 105 | [System.String] 106 | $Value 107 | ) 108 | 109 | $fullPath = Resolve-Path -Path $Path 110 | [IniFile]::WriteIniSetting($fullPath, $Section, $Key, $Value) 111 | } 112 | 113 | <# 114 | .SYNOPSIS 115 | Gets the value of an entry in an INI file. 116 | 117 | .PARAMETER Path 118 | The path to the INI file to get the entry value from. 119 | 120 | .PARAMETER Section 121 | The section to get the entry value from. 122 | 123 | .PARAMETER Key 124 | The name of the entry to get the value from. 125 | #> 126 | function Get-IniSettingFileValue 127 | { 128 | [CmdletBinding()] 129 | [OutputType([System.String])] 130 | param 131 | ( 132 | [Parameter(Mandatory = $true)] 133 | [System.String] 134 | $Path, 135 | 136 | [Parameter(Mandatory = $true)] 137 | [System.String] 138 | $Section, 139 | 140 | [Parameter(Mandatory = $true)] 141 | [System.String] 142 | $Key 143 | ) 144 | 145 | $fullPath = Resolve-Path -Path $Path 146 | return [IniFile]::GetIniSetting($fullPath, $Section, $Key, '') 147 | } 148 | 149 | <# 150 | .SYNOPSIS 151 | Gets file encoding. Defaults to ASCII. 152 | 153 | .DESCRIPTION 154 | The Get-FileEncoding function determines encoding by looking at Byte Order Mark (BOM). 155 | Based on port of C# code from http://www.west-wind.com/Weblog/posts/197245.aspx 156 | 157 | .EXAMPLE 158 | Get-ChildItem *.ps1 | select FullName, @{n='Encoding';e={Get-FileEncoding $_.FullName}} | where {$_.Encoding -ne 'ASCII'} 159 | This command gets ps1 files in current directory where encoding is not ASCII 160 | 161 | .EXAMPLE 162 | Get-ChildItem *.ps1 | select FullName, @{n='Encoding';e={Get-FileEncoding $_.FullName}} | where {$_.Encoding -ne 'ASCII'} | ` 163 | foreach {(get-content $_.FullName) | set-content $_.FullName -Encoding ASCII} 164 | Same as previous example but fixes encoding using set-content 165 | #> 166 | function Get-FileEncoding 167 | { 168 | [CmdletBinding()] 169 | [OutputType([System.String])] 170 | param 171 | ( 172 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 173 | [System.String] 174 | $Path 175 | ) 176 | 177 | [System.Byte[]] $byte = Get-Content -Encoding byte -ReadCount 4 -TotalCount 4 -Path $Path 178 | 179 | if ($byte[0] -eq 0xef -and $byte[1] -eq 0xbb -and $byte[2] -eq 0xbf) 180 | { 181 | return 'UTF8' 182 | } 183 | elseif ($byte[0] -eq 0xff -and $byte[1] -eq 0xfe) 184 | { 185 | return 'UTF32' 186 | } 187 | elseif ($byte[0] -eq 0xfe -and $byte[1] -eq 0xff) 188 | { 189 | return 'BigEndianUnicode' 190 | } 191 | elseif ($byte[0] -eq 0 -and $byte[1] -eq 0 -and $byte[2] -eq 0xfe -and $byte[3] -eq 0xff) 192 | { 193 | return 'BigEndianUTF32' 194 | } 195 | else 196 | { 197 | return 'ASCII' 198 | } 199 | } 200 | 201 | Export-ModuleMember -Function @( 202 | 'Get-TextEolCharacter', 203 | 'Set-IniSettingFileValue', 204 | 'Get-IniSettingFileValue', 205 | 'Get-FileEncoding' 206 | ) 207 | -------------------------------------------------------------------------------- /source/Modules/FileContentDsc.Common/en-US/FileContentDsc.Common.strings.psd1: -------------------------------------------------------------------------------- 1 | ConvertFrom-StringData @' 2 | '@ 3 | -------------------------------------------------------------------------------- /source/WikiSource/Home.md: -------------------------------------------------------------------------------- 1 | # Welcome to the FileContentDsc wiki 2 | 3 | *FileContentDsc v#.#.#* 4 | 5 | Here you will find all the information you need to make use of the FileContentDsc 6 | DSC resources, including details of the resources that are available, current 7 | capabilities and known issues, and information to help plan a DSC based 8 | implementation of FileContentDsc. 9 | 10 | Please leave comments, feature requests, and bug reports in then 11 | [issues section](https://github.com/dsccommunity/FileContentDsc/issues) for this 12 | module. 13 | 14 | ## Getting started 15 | 16 | To get started download FileContentDsc from the [PowerShell Gallery](http://www.powershellgallery.com/packages/FileContentDsc/) 17 | and then unzip it to one of your PowerShell modules folders 18 | (such as $env:ProgramFiles\WindowsPowerShell\Modules). 19 | 20 | To install from the PowerShell gallery using PowerShellGet (in PowerShell 5.0) 21 | run the following command: 22 | 23 | ```powershell 24 | Find-Module -Name FileContentDsc -Repository PSGallery | Install-Module 25 | ``` 26 | 27 | To confirm installation, run the below command and ensure you see the FileContentDsc 28 | DSC resources available: 29 | 30 | ```powershell 31 | Get-DscResource -Module FileContentDsc 32 | ``` 33 | 34 | ## Change Log 35 | 36 | A full list of changes in each version can be found in the [change log](https://github.com/dsccommunity/FileContentDsc/blob/main/CHANGELOG.md). 37 | -------------------------------------------------------------------------------- /source/en-US/about_FileContentDsc.help.txt: -------------------------------------------------------------------------------- 1 | TOPIC 2 | about_FileContentDsc 3 | 4 | SHORT DESCRIPTION 5 | DSC resources for for manipulating the content of text files. 6 | 7 | LONG DESCRIPTION 8 | This module contains DSC resources for for manipulating the content of text files. 9 | 10 | EXAMPLES 11 | PS C:\> Get-DscResource -Module FileContentDsc 12 | 13 | NOTE: 14 | Thank you to the DSC Community contributors who contributed to this module by 15 | writing code, sharing opinions, and provided feedback. 16 | 17 | TROUBLESHOOTING NOTE: 18 | Go to the Github repository for read about issues, submit a new issue, and read 19 | about new releases. https://github.com/dsccommunity/FileContentDsc 20 | 21 | SEE ALSO 22 | - https://github.com/dsccommunity/FileContentDsc 23 | 24 | KEYWORDS 25 | DSC, DscResource, FileContent 26 | -------------------------------------------------------------------------------- /tests/Integration/DSC_IniSettingsFile.Integration.Tests.ps1: -------------------------------------------------------------------------------- 1 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] 2 | param () 3 | 4 | $script:dscModuleName = 'FileContentDsc' 5 | $script:dscResourceName = 'DSC_IniSettingsFile' 6 | 7 | try 8 | { 9 | Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' 10 | } 11 | catch [System.IO.FileNotFoundException] 12 | { 13 | throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' 14 | } 15 | 16 | $script:testEnvironment = Initialize-TestEnvironment ` 17 | -DSCModuleName $script:dscModuleName ` 18 | -DSCResourceName $script:dscResourceName ` 19 | -ResourceType 'Mof' ` 20 | -TestType 'Integration' 21 | 22 | Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') 23 | 24 | try 25 | { 26 | Describe "$($script:dscResourceName)_Integration" { 27 | $script:confgurationFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'DSC_IniSettingsFile.config.ps1' 28 | $script:configurationName = 'IniSettingsFile' 29 | $script:testTextFile = Join-Path -Path $TestDrive -ChildPath 'TestFile.ini' 30 | $script:testText = 'Test Text' 31 | $script:testSecret = 'Test Secret' 32 | $script:testSection = 'Section One' 33 | $script:testKey = 'SettingTwo' 34 | $script:testSecureSecret = ConvertTo-SecureString -String $script:testSecret -AsPlainText -Force 35 | $script:testSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ('Dummy', $script:testSecureSecret) 36 | 37 | $script:testFileContent = @" 38 | [Section One] 39 | SettingOne=Value1 40 | SettingTwo=Value2 41 | 42 | [Section Two] 43 | SettingThree=Value3 44 | 45 | "@ 46 | 47 | $script:testFileExpectedTextContent = @" 48 | [Section One] 49 | SettingOne=Value1 50 | SettingTwo=$($script:testText) 51 | 52 | [Section Two] 53 | SettingThree=Value3 54 | 55 | "@ 56 | 57 | $script:testFileExpectedSecretContent = @" 58 | [Section One] 59 | SettingOne=Value1 60 | SettingTwo=$($script:testSecret) 61 | 62 | [Section Two] 63 | SettingThree=Value3 64 | 65 | "@ 66 | 67 | # Load the DSC config to use for testing 68 | . $script:confgurationFilePath -ConfigurationName $script:configurationName 69 | 70 | Context 'An INI settings file containing an entry to be replaced with another text string' { 71 | BeforeAll { 72 | # Create the text file to use for testing 73 | Set-Content ` 74 | -Path $script:testTextFile ` 75 | -Value $script:testFileContent ` 76 | -NoNewline ` 77 | -Force 78 | } 79 | 80 | #region DEFAULT TESTS 81 | It 'Should compile and apply the MOF without throwing' { 82 | { 83 | $configData = @{ 84 | AllNodes = @( 85 | @{ 86 | NodeName = 'localhost' 87 | Path = $script:testTextFile 88 | Section = $script:testSection 89 | Key = $script:testKey 90 | Type = 'Text' 91 | Text = $script:testText 92 | } 93 | ) 94 | } 95 | 96 | & $script:configurationName ` 97 | -OutputPath $TestDrive ` 98 | -ConfigurationData $configData 99 | 100 | Start-DscConfiguration ` 101 | -Path $TestDrive ` 102 | -ComputerName localhost ` 103 | -Wait ` 104 | -Verbose ` 105 | -Force ` 106 | -ErrorAction Stop 107 | } | Should -Not -Throw 108 | } 109 | 110 | It 'Should be able to call Get-DscConfiguration without throwing' { 111 | { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw 112 | } 113 | 114 | It 'Should have set the resource and all the parameters should match' { 115 | $script:current = $script:currentDscConfig | Where-Object { 116 | $_.ConfigurationName -eq $script:configurationName 117 | } 118 | $current.Path | Should -Be $script:testTextFile 119 | $current.Section | Should -Be $script:testSection 120 | $current.Key | Should -Be $script:testKey 121 | $current.Type | Should -Be 'Text' 122 | $current.Text | Should -Be $script:testText 123 | } 124 | 125 | It 'Should be convert the file content to match expected content' { 126 | Get-Content -Path $script:testTextFile -Raw | Should -Be $script:testFileExpectedTextContent 127 | } 128 | 129 | AfterAll { 130 | if (Test-Path -Path $script:testTextFile) 131 | { 132 | Remove-Item -Path $script:testTextFile -Force 133 | } 134 | } 135 | } 136 | 137 | Context 'An INI settings file containing an entry to be replaced with another secret text string' { 138 | BeforeAll { 139 | # Create the text file to use for testing 140 | Set-Content ` 141 | -Path $script:testTextFile ` 142 | -Value $script:testFileContent ` 143 | -NoNewline ` 144 | -Force 145 | } 146 | 147 | #region DEFAULT TESTS 148 | It 'Should compile and apply the MOF without throwing' { 149 | { 150 | $configData = @{ 151 | AllNodes = @( 152 | @{ 153 | NodeName = 'localhost' 154 | Path = $script:testTextFile 155 | Section = $script:testSection 156 | Key = $script:testKey 157 | Type = 'Secret' 158 | Secret = $script:testSecretCredential 159 | PsDscAllowPlainTextPassword = $true 160 | } 161 | ) 162 | } 163 | 164 | & $script:configurationName ` 165 | -OutputPath $TestDrive ` 166 | -ConfigurationData $configData 167 | 168 | Start-DscConfiguration ` 169 | -Path $TestDrive ` 170 | -ComputerName localhost ` 171 | -Wait ` 172 | -Verbose ` 173 | -Force ` 174 | -ErrorAction Stop 175 | } | Should -Not -Throw 176 | } 177 | 178 | It 'Should be able to call Get-DscConfiguration without throwing' { 179 | { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw 180 | } 181 | 182 | It 'Should have set the resource and all the parameters should match' { 183 | $script:current = $script:currentDscConfig | Where-Object { 184 | $_.ConfigurationName -eq $script:configurationName 185 | } 186 | $current.Path | Should -Be $script:testTextFile 187 | $current.Section | Should -Be $script:testSection 188 | $current.Key | Should -Be $script:testKey 189 | $current.Type | Should -Be 'Text' 190 | $current.Text | Should -Be $script:testSecret 191 | } 192 | 193 | It 'Should be convert the file content to match expected content' { 194 | Get-Content -Path $script:testTextFile -Raw | Should -Be $script:testFileExpectedSecretContent 195 | } 196 | 197 | AfterAll { 198 | if (Test-Path -Path $script:testTextFile) 199 | { 200 | Remove-Item -Path $script:testTextFile -Force 201 | } 202 | } 203 | } 204 | } 205 | } 206 | finally 207 | { 208 | Restore-TestEnvironment -TestEnvironment $script:testEnvironment 209 | } 210 | -------------------------------------------------------------------------------- /tests/Integration/DSC_IniSettingsFile.config.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | [Parameter(Mandatory = $true)] 4 | [System.String] 5 | $ConfigurationName 6 | ) 7 | 8 | Configuration $ConfigurationName 9 | { 10 | Import-DscResource -ModuleName FileContentDsc 11 | 12 | Node localhost { 13 | if ($Node.Type -eq 'Text') 14 | { 15 | IniSettingsFile ReplaceTextIntegrationTest 16 | { 17 | Path = $Node.Path 18 | Section = $Node.Section 19 | Key = $Node.Key 20 | Text = $Node.Text 21 | } 22 | } 23 | else 24 | { 25 | IniSettingsFile ReplaceTextIntegrationTest 26 | { 27 | Path = $Node.Path 28 | Section = $Node.Section 29 | Key = $Node.Key 30 | Type = $Node.Type 31 | Secret = $Node.Secret 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Integration/DSC_KeyValuePairFile.Integration.Tests.ps1: -------------------------------------------------------------------------------- 1 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] 2 | param () 3 | 4 | $script:dscModuleName = 'FileContentDsc' 5 | $script:dscResourceName = 'DSC_KeyValuePairFile' 6 | 7 | try 8 | { 9 | Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' 10 | } 11 | catch [System.IO.FileNotFoundException] 12 | { 13 | throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' 14 | } 15 | 16 | $script:testEnvironment = Initialize-TestEnvironment ` 17 | -DSCModuleName $script:dscModuleName ` 18 | -DSCResourceName $script:dscResourceName ` 19 | -ResourceType 'Mof' ` 20 | -TestType 'Integration' 21 | 22 | Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') 23 | 24 | try 25 | { 26 | Describe "$($script:dscResourceName)_Integration" { 27 | $script:confgurationFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'DSC_KeyValuePairFile.config.ps1' 28 | $script:configurationName = 'KeyValuePairFile' 29 | $script:testTextFile = Join-Path -Path $TestDrive -ChildPath 'TestFile.txt' 30 | $script:testName = 'Setting.Two' 31 | $script:testText = 'Test Text' 32 | $script:testSecret = 'Test Secret' 33 | $script:testSecureSecret = ConvertTo-SecureString -String $script:testSecret -AsPlainText -Force 34 | $script:testSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ('Dummy', $script:testSecureSecret) 35 | 36 | $script:fileEncodingParameters = @{ 37 | Path = $script:testTextFile 38 | Encoding = 'ASCII' 39 | } 40 | 41 | $script:testNonCompliantEncoding = @{ 42 | Path = $script:fileEncodingParameters.Path 43 | Encoding = 'UTF8' 44 | } 45 | 46 | $script:testFileContent = @" 47 | Setting1=Value1 48 | $($script:testName)=Value 2 49 | $($script:testName)=Value 3 50 | $($script:testName)=$($script:testText) 51 | Setting3.Test=Value4 52 | 53 | "@ 54 | 55 | $script:testFileExpectedTextContent = @" 56 | Setting1=Value1 57 | $($script:testName)=$($script:testText) 58 | $($script:testName)=$($script:testText) 59 | $($script:testName)=$($script:testText) 60 | Setting3.Test=Value4 61 | 62 | "@ 63 | 64 | $script:testFileExpectedSecretContent = @" 65 | Setting1=Value1 66 | $($script:testName)=$($script:testSecret) 67 | $($script:testName)=$($script:testSecret) 68 | $($script:testName)=$($script:testSecret) 69 | Setting3.Test=Value4 70 | 71 | "@ 72 | 73 | $script:testFileExpectedAbsentContent = @" 74 | Setting1=Value1 75 | Setting3.Test=Value4 76 | 77 | "@ 78 | 79 | $script:testFileExpectedEmptyContent = @" 80 | $($script:testName)=$($script:testText) 81 | "@ 82 | 83 | # Load the DSC config to use for testing 84 | . $script:confgurationFilePath -ConfigurationName $script:configurationName 85 | 86 | Context 'When the key value pair text file contains a key value to be replaced with a text string' { 87 | BeforeAll { 88 | # Create the text file to use for testing 89 | Set-Content ` 90 | -Path $script:testTextFile ` 91 | -Value $script:testFileContent ` 92 | -NoNewline ` 93 | -Force 94 | } 95 | 96 | It 'Should compile and apply the MOF without throwing' { 97 | { 98 | $configData = @{ 99 | AllNodes = @( 100 | @{ 101 | NodeName = 'localhost' 102 | Path = $script:testTextFile 103 | Name = $script:testName 104 | Ensure = 'Present' 105 | Type = 'Text' 106 | Text = $script:testText 107 | } 108 | ) 109 | } 110 | 111 | & $script:configurationName ` 112 | -OutputPath $TestDrive ` 113 | -ConfigurationData $configData 114 | 115 | Start-DscConfiguration ` 116 | -Path $TestDrive ` 117 | -ComputerName localhost ` 118 | -Wait ` 119 | -Verbose ` 120 | -Force ` 121 | -ErrorAction Stop 122 | } | Should -Not -Throw 123 | } 124 | 125 | It 'Should be able to call Get-DscConfiguration without throwing' { 126 | { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw 127 | } 128 | 129 | It 'Should have set the resource and all the parameters should match' { 130 | $script:current = $script:currentDscConfig | Where-Object { 131 | $_.ConfigurationName -eq $script:configurationName 132 | } 133 | $current.Path | Should -Be $script:testTextFile 134 | $current.Name | Should -Be $script:testName 135 | $current.Ensure | Should -Be 'Present' 136 | $current.Type | Should -Be 'Text' 137 | $current.Text | Should -Be "$($script:testText),$($script:testText),$($script:testText)" 138 | } 139 | 140 | It 'Should be convert the file content to match expected content' { 141 | Get-Content -Path $script:testTextFile -Raw | Should -Be $script:testFileExpectedTextContent 142 | } 143 | 144 | AfterAll { 145 | if (Test-Path -Path $script:testTextFile) 146 | { 147 | Remove-Item -Path $script:testTextFile -Force 148 | } 149 | } 150 | } 151 | 152 | Context 'When the key value pair text file contains a key value to be replaced with a secret text string' { 153 | BeforeAll { 154 | # Create the text file to use for testing 155 | Set-Content ` 156 | -Path $script:testTextFile ` 157 | -Value $script:testFileContent ` 158 | -NoNewline ` 159 | -Force 160 | } 161 | 162 | It 'Should compile and apply the MOF without throwing' { 163 | { 164 | $configData = @{ 165 | AllNodes = @( 166 | @{ 167 | NodeName = 'localhost' 168 | Path = $script:testTextFile 169 | Name = $script:testName 170 | Ensure = 'Present' 171 | Type = 'Secret' 172 | Secret = $script:testSecretCredential 173 | PsDscAllowPlainTextPassword = $true 174 | } 175 | ) 176 | } 177 | 178 | & $script:configurationName ` 179 | -OutputPath $TestDrive ` 180 | -ConfigurationData $configData 181 | 182 | Start-DscConfiguration ` 183 | -Path $TestDrive ` 184 | -ComputerName localhost ` 185 | -Wait ` 186 | -Verbose ` 187 | -Force ` 188 | -ErrorAction Stop 189 | } | Should -Not -Throw 190 | } 191 | 192 | It 'Should be able to call Get-DscConfiguration without throwing' { 193 | { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw 194 | } 195 | 196 | It 'Should have set the resource and all the parameters should match' { 197 | $script:current = $script:currentDscConfig | Where-Object { 198 | $_.ConfigurationName -eq $script:configurationName 199 | } 200 | $current.Path | Should -Be $script:testTextFile 201 | $current.Name | Should -Be $script:testName 202 | $current.Ensure | Should -Be 'Present' 203 | $current.Type | Should -Be 'Text' 204 | $current.Text | Should -Be "$($script:testSecret),$($script:testSecret),$($script:testSecret)" 205 | } 206 | 207 | It 'Should be convert the file content to match expected content' { 208 | Get-Content -Path $script:testTextFile -Raw | Should -Be $script:testFileExpectedSecretContent 209 | } 210 | 211 | AfterAll { 212 | if (Test-Path -Path $script:testTextFile) 213 | { 214 | Remove-Item -Path $script:testTextFile -Force 215 | } 216 | } 217 | } 218 | 219 | Context 'When the key value pair text file contains a key that needs to be removed' { 220 | BeforeAll { 221 | # Create the text file to use for testing 222 | Set-Content ` 223 | -Path $script:testTextFile ` 224 | -Value $script:testFileContent ` 225 | -NoNewline ` 226 | -Force 227 | } 228 | 229 | It 'Should compile and apply the MOF without throwing' { 230 | { 231 | $configData = @{ 232 | AllNodes = @( 233 | @{ 234 | NodeName = 'localhost' 235 | Path = $script:testTextFile 236 | Name = $script:testName 237 | Ensure = 'Absent' 238 | } 239 | ) 240 | } 241 | 242 | & $script:configurationName ` 243 | -OutputPath $TestDrive ` 244 | -ConfigurationData $configData 245 | 246 | Start-DscConfiguration ` 247 | -Path $TestDrive ` 248 | -ComputerName localhost ` 249 | -Wait ` 250 | -Verbose ` 251 | -Force ` 252 | -ErrorAction Stop 253 | } | Should -Not -Throw 254 | } 255 | 256 | It 'Should be able to call Get-DscConfiguration without throwing' { 257 | { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw 258 | } 259 | 260 | It 'Should have set the resource and all the parameters should match' { 261 | $script:current = $script:currentDscConfig | Where-Object { 262 | $_.ConfigurationName -eq $script:configurationName 263 | } 264 | $current.Path | Should -Be $script:testTextFile 265 | $current.Name | Should -Be $script:testName 266 | $current.Ensure | Should -Be 'Absent' 267 | $current.Type | Should -Be 'Text' 268 | $current.Text | Should -BeNullOrEmpty 269 | } 270 | 271 | It 'Should be convert the file content to match expected content' { 272 | Get-Content -Path $script:testTextFile -Raw | Should -Be $script:testFileExpectedAbsentContent 273 | } 274 | 275 | AfterAll { 276 | if (Test-Path -Path $script:testTextFile) 277 | { 278 | Remove-Item -Path $script:testTextFile -Force 279 | } 280 | } 281 | } 282 | 283 | Context 'When the key value pair text file requires encoding be changed' { 284 | BeforeAll { 285 | # Create the text file to use for testing 286 | Set-Content ` 287 | -Path $script:testTextFile ` 288 | -Value $script:testFileContent ` 289 | -Encoding $script:testNonCompliantEncoding.Encoding ` 290 | -NoNewline ` 291 | -Force 292 | } 293 | 294 | It 'Should compile and apply the MOF without throwing' { 295 | { 296 | $configData = @{ 297 | AllNodes = @( 298 | @{ 299 | NodeName = 'localhost' 300 | Path = $script:testTextFile 301 | Name = $script:testName 302 | Ensure = 'Present' 303 | Type = 'Text' 304 | Text = $script:testText 305 | Encoding = $script:fileEncodingParameters.Encoding 306 | } 307 | ) 308 | } 309 | 310 | & $script:configurationName ` 311 | -OutputPath $TestDrive ` 312 | -ConfigurationData $configData 313 | 314 | Start-DscConfiguration ` 315 | -Path $TestDrive ` 316 | -ComputerName localhost ` 317 | -Wait ` 318 | -Verbose ` 319 | -Force ` 320 | -ErrorAction Stop 321 | } | Should -Not -Throw 322 | } 323 | 324 | It 'Should be able to call Get-DscConfiguration without throwing' { 325 | { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw 326 | } 327 | 328 | It 'Should have set the resource and all the parameters should match' { 329 | $script:current = $script:currentDscConfig | Where-Object { 330 | $_.ConfigurationName -eq $script:configurationName 331 | } 332 | $current.Path | Should -Be $script:testTextFile 333 | $current.Name | Should -Be $script:testName 334 | $current.Ensure | Should -Be 'Present' 335 | $current.Type | Should -Be 'Text' 336 | $current.Text | Should -Be "$($script:testText),$($script:testText),$($script:testText)" 337 | $current.Encoding | Should -Be $script:fileEncodingParameters.Encoding 338 | } 339 | 340 | It 'Should convert file encoding to the expected type' { 341 | Get-FileEncoding -Path $script:testTextFile | Should -Be $script:fileEncodingParameters.Encoding 342 | } 343 | 344 | AfterAll { 345 | if (Test-Path -Path $script:testTextFile) 346 | { 347 | Remove-Item -Path $script:testTextFile -Force 348 | } 349 | } 350 | } 351 | 352 | Context 'When the key value pair text file does not exist' { 353 | BeforeAll { 354 | # Make sure the test file does not exist 355 | Remove-Item -Path $script:testTextFile -Force -ErrorAction SilentlyContinue 356 | } 357 | 358 | It 'Should compile and apply the MOF without throwing' { 359 | { 360 | $configData = @{ 361 | AllNodes = @( 362 | @{ 363 | NodeName = 'localhost' 364 | Path = $script:testTextFile 365 | Name = $script:testName 366 | Ensure = 'Present' 367 | Type = 'Text' 368 | Text = $script:testText 369 | } 370 | ) 371 | } 372 | 373 | & $script:configurationName ` 374 | -OutputPath $TestDrive ` 375 | -ConfigurationData $configData 376 | 377 | Start-DscConfiguration ` 378 | -Path $TestDrive ` 379 | -ComputerName localhost ` 380 | -Wait ` 381 | -Verbose ` 382 | -Force ` 383 | -ErrorAction Stop 384 | } | Should -Not -Throw 385 | } 386 | 387 | It 'Should be able to call Get-DscConfiguration without throwing' { 388 | { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw 389 | } 390 | 391 | It 'Should have set the resource and all the parameters should match' { 392 | $script:current = $script:currentDscConfig | Where-Object { 393 | $_.ConfigurationName -eq $script:configurationName 394 | } 395 | $current.Path | Should -Be $script:testTextFile 396 | $current.Name | Should -Be $script:testName 397 | $current.Ensure | Should -Be 'Present' 398 | $current.Type | Should -Be 'Text' 399 | $current.Text | Should -Be $script:testText 400 | } 401 | 402 | It 'Should be convert the file content to match expected content' { 403 | Get-Content -Path $script:testTextFile -Raw | Should -Be $script:testFileExpectedEmptyContent 404 | } 405 | 406 | AfterAll { 407 | if (Test-Path -Path $script:testTextFile) 408 | { 409 | Remove-Item -Path $script:testTextFile -Force 410 | } 411 | } 412 | } 413 | 414 | Context 'When the key value pair text file that is empty' { 415 | BeforeAll { 416 | # Make sure the test file is empty 417 | Set-Content ` 418 | -Path $script:testTextFile ` 419 | -Value '' ` 420 | -NoNewline ` 421 | -Force 422 | } 423 | 424 | It 'Should compile and apply the MOF without throwing' { 425 | { 426 | $configData = @{ 427 | AllNodes = @( 428 | @{ 429 | NodeName = 'localhost' 430 | Path = $script:testTextFile 431 | Name = $script:testName 432 | Ensure = 'Present' 433 | Type = 'Text' 434 | Text = $script:testText 435 | } 436 | ) 437 | } 438 | 439 | & $script:configurationName ` 440 | -OutputPath $TestDrive ` 441 | -ConfigurationData $configData 442 | 443 | Start-DscConfiguration ` 444 | -Path $TestDrive ` 445 | -ComputerName localhost ` 446 | -Wait ` 447 | -Verbose ` 448 | -Force ` 449 | -ErrorAction Stop 450 | } | Should -Not -Throw 451 | } 452 | 453 | It 'Should be able to call Get-DscConfiguration without throwing' { 454 | { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw 455 | } 456 | 457 | It 'Should have set the resource and all the parameters should match' { 458 | $script:current = $script:currentDscConfig | Where-Object { 459 | $_.ConfigurationName -eq $script:configurationName 460 | } 461 | $current.Path | Should -Be $script:testTextFile 462 | $current.Name | Should -Be $script:testName 463 | $current.Ensure | Should -Be 'Present' 464 | $current.Type | Should -Be 'Text' 465 | $current.Text | Should -Be $script:testText 466 | } 467 | 468 | It 'Should be convert the file content to match expected content' { 469 | Get-Content -Path $script:testTextFile -Raw | Should -Be $script:testFileExpectedEmptyContent 470 | } 471 | 472 | AfterAll { 473 | if (Test-Path -Path $script:testTextFile) 474 | { 475 | Remove-Item -Path $script:testTextFile -Force 476 | } 477 | } 478 | } 479 | } 480 | } 481 | finally 482 | { 483 | Restore-TestEnvironment -TestEnvironment $script:testEnvironment 484 | } 485 | -------------------------------------------------------------------------------- /tests/Integration/DSC_KeyValuePairFile.config.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | [Parameter(Mandatory = $true)] 4 | [System.String] 5 | $ConfigurationName 6 | ) 7 | 8 | Configuration $ConfigurationName 9 | { 10 | Import-DscResource -ModuleName FileContentDsc 11 | 12 | Node localhost { 13 | if ($Node.Ensure -eq 'Absent') 14 | { 15 | KeyValuePairFile KeyValuePairFileIntegrationTest 16 | { 17 | Path = $Node.Path 18 | Name = $Node.Name 19 | Ensure = $Node.Ensure 20 | } 21 | } 22 | elseif ($Node.Type -eq 'Text') 23 | { 24 | KeyValuePairFile KeyValuePairFileIntegrationTest 25 | { 26 | Path = $Node.Path 27 | Name = $Node.Name 28 | Ensure = $Node.Ensure 29 | Type = $Node.Type 30 | Text = $Node.Text 31 | } 32 | } 33 | elseif (($Node.Type -eq 'Text') -and ($Node.Encoding -eq 'ASCII')) 34 | { 35 | KeyValuePairFile KeyValuePairFileIntegrationTest 36 | { 37 | Path = $Node.Path 38 | Name = $Node.Name 39 | Ensure = $Node.Ensure 40 | Type = $Node.Type 41 | Text = $Node.Text 42 | Encoding = $Node.Encoding 43 | } 44 | } 45 | else 46 | { 47 | KeyValuePairFile KeyValuePairFileIntegrationTest 48 | { 49 | Path = $Node.Path 50 | Name = $Node.Name 51 | Ensure = $Node.Ensure 52 | Type = $Node.Type 53 | Secret = $Node.Secret 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/Integration/DSC_ReplaceText.Integration.Tests.ps1: -------------------------------------------------------------------------------- 1 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] 2 | param () 3 | 4 | $script:dscModuleName = 'FileContentDsc' 5 | $script:dscResourceName = 'DSC_ReplaceText' 6 | 7 | try 8 | { 9 | Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' 10 | } 11 | catch [System.IO.FileNotFoundException] 12 | { 13 | throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' 14 | } 15 | 16 | $script:testEnvironment = Initialize-TestEnvironment ` 17 | -DSCModuleName $script:dscModuleName ` 18 | -DSCResourceName $script:dscResourceName ` 19 | -ResourceType 'Mof' ` 20 | -TestType 'Integration' 21 | 22 | Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') 23 | 24 | try 25 | { 26 | Describe "$($script:dscResourceName)_Integration" { 27 | $script:confgurationFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'DSC_ReplaceText.config.ps1' 28 | $script:configurationName = 'ReplaceText' 29 | $script:testTextFile = Join-Path -Path $TestDrive -ChildPath 'TestFile.txt' 30 | $script:testText = 'TestText' 31 | $script:testSecret = 'TestSecret' 32 | $script:testSearch = "Setting\.Two='(.)*'" 33 | $script:testTextReplace = "Setting.Two='$($script:testText)'" 34 | $script:testSecretReplace = "Setting.Two='$($script:testSecret)'" 35 | $script:testSecureSecretReplace = ConvertTo-SecureString -String $script:testSecretReplace -AsPlainText -Force 36 | $script:testSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ('Dummy', $script:testSecureSecretReplace) 37 | 38 | $script:fileEncodingParameters = @{ 39 | Path = $script:testTextFile 40 | Encoding = 'ASCII' 41 | } 42 | 43 | $script:testNonCompliantEncoding = @{ 44 | Path = $script:fileEncodingParameters.Path 45 | Encoding = 'UTF8' 46 | } 47 | 48 | $script:testFileContent = @" 49 | Setting1=Value1 50 | Setting.Two='Value2' 51 | Setting.Two='Value3' 52 | Setting.Two='$($script:testText)' 53 | Setting3.Test=Value4 54 | 55 | "@ 56 | 57 | $script:testFileExpectedTextContent = @" 58 | Setting1=Value1 59 | Setting.Two='$($script:testText)' 60 | Setting.Two='$($script:testText)' 61 | Setting.Two='$($script:testText)' 62 | Setting3.Test=Value4 63 | 64 | "@ 65 | 66 | $script:testFileExpectedSecretContent = @" 67 | Setting1=Value1 68 | Setting.Two='$($script:testSecret)' 69 | Setting.Two='$($script:testSecret)' 70 | Setting.Two='$($script:testSecret)' 71 | Setting3.Test=Value4 72 | 73 | "@ 74 | 75 | # Load the DSC config to use for testing 76 | . $script:confgurationFilePath -ConfigurationName $script:configurationName 77 | 78 | Context 'A text file containing text to be replaced with another text string' { 79 | BeforeAll { 80 | # Create the text file to use for testing 81 | Set-Content ` 82 | -Path $script:testTextFile ` 83 | -Value $script:testFileContent ` 84 | -NoNewline ` 85 | -Force 86 | } 87 | 88 | #region DEFAULT TESTS 89 | It 'Should compile and apply the MOF without throwing' { 90 | { 91 | $configData = @{ 92 | AllNodes = @( 93 | @{ 94 | NodeName = 'localhost' 95 | Path = $script:testTextFile 96 | Search = $script:testSearch 97 | Type = 'Text' 98 | Text = $script:testTextReplace 99 | } 100 | ) 101 | } 102 | 103 | & $script:configurationName ` 104 | -OutputPath $TestDrive ` 105 | -ConfigurationData $configData 106 | 107 | Start-DscConfiguration ` 108 | -Path $TestDrive ` 109 | -ComputerName localhost ` 110 | -Wait ` 111 | -Verbose ` 112 | -Force ` 113 | -ErrorAction Stop 114 | } | Should -Not -Throw 115 | } 116 | 117 | It 'Should be able to call Get-DscConfiguration without throwing' { 118 | { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw 119 | } 120 | 121 | It 'Should have set the resource and all the parameters should match' { 122 | $script:current = $script:currentDscConfig | Where-Object { 123 | $_.ConfigurationName -eq $script:configurationName 124 | } 125 | $current.Path | Should -Be $script:testTextFile 126 | $current.Search | Should -Be $script:testSearch 127 | $current.Type | Should -Be 'Text' 128 | $current.Text | Should -Be "$($script:testTextReplace),$($script:testTextReplace),$($script:testTextReplace)" 129 | } 130 | 131 | It 'Should be convert the file content to match expected content' { 132 | Get-Content -Path $script:testTextFile -Raw | Should -Be $script:testFileExpectedTextContent 133 | } 134 | 135 | AfterAll { 136 | if (Test-Path -Path $script:testTextFile) 137 | { 138 | Remove-Item -Path $script:testTextFile -Force 139 | } 140 | } 141 | } 142 | 143 | Context 'A text file containing text to be replaced with secret text' { 144 | BeforeAll { 145 | # Create the text file to use for testing 146 | Set-Content ` 147 | -Path $script:testTextFile ` 148 | -Value $script:testFileContent ` 149 | -NoNewline ` 150 | -Force 151 | } 152 | 153 | #region DEFAULT TESTS 154 | It 'Should compile and apply the MOF without throwing' { 155 | { 156 | $configData = @{ 157 | AllNodes = @( 158 | @{ 159 | NodeName = 'localhost' 160 | Path = $script:testTextFile 161 | Search = $script:testSearch 162 | Type = 'Secret' 163 | Secret = $script:testSecretCredential 164 | PsDscAllowPlainTextPassword = $true 165 | } 166 | ) 167 | } 168 | 169 | & $script:configurationName ` 170 | -OutputPath $TestDrive ` 171 | -ConfigurationData $configData 172 | 173 | Start-DscConfiguration ` 174 | -Path $TestDrive ` 175 | -ComputerName localhost ` 176 | -Wait ` 177 | -Verbose ` 178 | -Force ` 179 | -ErrorAction Stop 180 | } | Should -Not -Throw 181 | } 182 | 183 | It 'Should be able to call Get-DscConfiguration without throwing' { 184 | { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw 185 | } 186 | 187 | It 'Should have set the resource and all the parameters should match' { 188 | $script:current = $script:currentDscConfig | Where-Object { 189 | $_.ConfigurationName -eq $script:configurationName 190 | } 191 | $current.Path | Should -Be $script:testTextFile 192 | $current.Search | Should -Be $script:testSearch 193 | $current.Type | Should -Be 'Text' 194 | $current.Text | Should -Be "$($script:testSecretReplace),$($script:testSecretReplace),$($script:testSecretReplace)" 195 | } 196 | 197 | It 'Should be convert the file content to match expected content' { 198 | Get-Content -Path $script:testTextFile -Raw | Should -Be $script:testFileExpectedSecretContent 199 | } 200 | 201 | AfterAll { 202 | if (Test-Path -Path $script:testTextFile) 203 | { 204 | Remove-Item -Path $script:testTextFile -Force 205 | } 206 | } 207 | } 208 | 209 | Context 'A text file that requires encoding be changed' { 210 | BeforeAll { 211 | # Create the text file to use for testing 212 | Set-Content ` 213 | -Path $script:testTextFile ` 214 | -Value $script:testFileContent ` 215 | -Encoding $script:testNonCompliantEncoding.Encoding ` 216 | -NoNewline ` 217 | -Force 218 | } 219 | 220 | #region DEFAULT TESTS 221 | It 'Should compile and apply the MOF without throwing' { 222 | { 223 | $configData = @{ 224 | AllNodes = @( 225 | @{ 226 | NodeName = 'localhost' 227 | Path = $script:testTextFile 228 | Search = $script:testSearch 229 | Type = 'Text' 230 | Text = $script:testTextReplace 231 | Encoding = $script:fileEncodingParameters.Encoding 232 | } 233 | ) 234 | } 235 | 236 | & $script:configurationName ` 237 | -OutputPath $TestDrive ` 238 | -ConfigurationData $configData 239 | 240 | Start-DscConfiguration ` 241 | -Path $TestDrive ` 242 | -ComputerName localhost ` 243 | -Wait ` 244 | -Verbose ` 245 | -Force ` 246 | -ErrorAction Stop 247 | } | Should -Not -Throw 248 | } 249 | 250 | It 'Should be able to call Get-DscConfiguration without throwing' { 251 | { $script:currentDscConfig = Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -throw 252 | } 253 | 254 | It 'Should have set the resource and all the parameters should match' { 255 | $script:current = $script:currentDscConfig | Where-Object { 256 | $_.ConfigurationName -eq $script:configurationName 257 | } 258 | $current.Path | Should -Be $script:testTextFile 259 | $current.Search | Should -Be $script:testSearch 260 | $current.Type | Should -Be 'Text' 261 | $current.Text | Should -Be "$($script:testTextReplace),$($script:testTextReplace),$($script:testTextReplace)" 262 | $current.Encoding | Should -Be $script:fileEncodingParameters.Encoding 263 | } 264 | 265 | It 'Should convert file encoding to the expected type' { 266 | Get-FileEncoding -Path $script:testTextFile | Should -Be $script:fileEncodingParameters.Encoding 267 | } 268 | 269 | AfterAll { 270 | if (Test-Path -Path $script:testTextFile) 271 | { 272 | Remove-Item -Path $script:testTextFile -Force 273 | } 274 | } 275 | } 276 | } 277 | } 278 | finally 279 | { 280 | Restore-TestEnvironment -TestEnvironment $script:testEnvironment 281 | } 282 | -------------------------------------------------------------------------------- /tests/Integration/DSC_ReplaceText.config.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | [Parameter(Mandatory = $true)] 4 | [System.String] 5 | $ConfigurationName 6 | ) 7 | 8 | Configuration $ConfigurationName 9 | { 10 | Import-DscResource -ModuleName FileContentDsc 11 | 12 | Node localhost { 13 | if ($Node.Type -eq 'Text') 14 | { 15 | ReplaceText ReplaceTextIntegrationTest 16 | { 17 | Path = $Node.Path 18 | Search = $Node.Search 19 | Type = $Node.Type 20 | Text = $Node.Text 21 | } 22 | } 23 | elseif (($Node.Type -eq 'Text') -and ($Node.Encoding -eq 'ASCII')) 24 | { 25 | ReplaceText ReplaceTextIntegrationTest 26 | { 27 | Path = $Node.Path 28 | Search = $Node.Search 29 | Type = $Node.Type 30 | Text = $Node.Text 31 | Encoding = $Node.Encoding 32 | } 33 | } 34 | else 35 | { 36 | ReplaceText ReplaceTextIntegrationTest 37 | { 38 | Path = $Node.Path 39 | Search = $Node.Search 40 | Type = $Node.Type 41 | Secret = $Node.Secret 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/TestHelpers/CommonTestHelper.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns an invalid argument exception object 4 | 5 | .PARAMETER Message 6 | The message explaining why this error is being thrown 7 | 8 | .PARAMETER ArgumentName 9 | The name of the invalid argument that is causing this error to be thrown 10 | #> 11 | function Get-InvalidArgumentRecord 12 | { 13 | [CmdletBinding()] 14 | param 15 | ( 16 | [Parameter(Mandatory = $true)] 17 | [ValidateNotNullOrEmpty()] 18 | [System.String] 19 | $Message, 20 | 21 | [Parameter(Mandatory = $true)] 22 | [ValidateNotNullOrEmpty()] 23 | [System.String] 24 | $ArgumentName 25 | ) 26 | 27 | $argumentException = New-Object -TypeName 'ArgumentException' -ArgumentList @( $Message, 28 | $ArgumentName ) 29 | $newObjectParams = @{ 30 | TypeName = 'System.Management.Automation.ErrorRecord' 31 | ArgumentList = @( $argumentException, $ArgumentName, 'InvalidArgument', $null ) 32 | } 33 | return New-Object @newObjectParams 34 | } 35 | 36 | <# 37 | .SYNOPSIS 38 | Returns an invalid operation exception object 39 | 40 | .PARAMETER Message 41 | The message explaining why this error is being thrown 42 | 43 | .PARAMETER ErrorRecord 44 | The error record containing the exception that is causing this terminating error 45 | #> 46 | function Get-InvalidOperationRecord 47 | { 48 | [CmdletBinding()] 49 | param 50 | ( 51 | [ValidateNotNullOrEmpty()] 52 | [System.String] 53 | $Message, 54 | 55 | [ValidateNotNull()] 56 | [System.Management.Automation.ErrorRecord] 57 | $ErrorRecord 58 | ) 59 | 60 | if ($null -eq $Message) 61 | { 62 | $invalidOperationException = New-Object -TypeName 'InvalidOperationException' 63 | } 64 | elseif ($null -eq $ErrorRecord) 65 | { 66 | $invalidOperationException = 67 | New-Object -TypeName 'InvalidOperationException' -ArgumentList @( $Message ) 68 | } 69 | else 70 | { 71 | $invalidOperationException = 72 | New-Object -TypeName 'InvalidOperationException' -ArgumentList @( $Message, 73 | $ErrorRecord.Exception ) 74 | } 75 | 76 | $newObjectParams = @{ 77 | TypeName = 'System.Management.Automation.ErrorRecord' 78 | ArgumentList = @( $invalidOperationException.ToString(), 'MachineStateIncorrect', 79 | 'InvalidOperation', $null ) 80 | } 81 | return New-Object @newObjectParams 82 | } 83 | 84 | <# 85 | .SYNOPSIS 86 | Gets file encoding. Defaults to ASCII. 87 | 88 | .DESCRIPTION 89 | The Get-FileEncoding function determines encoding by looking at Byte Order Mark (BOM). 90 | Based on port of C# code from http://www.west-wind.com/Weblog/posts/197245.aspx 91 | 92 | .EXAMPLE 93 | Get-ChildItem *.ps1 | select FullName, @{n='Encoding';e={Get-FileEncoding $_.FullName}} | where {$_.Encoding -ne 'ASCII'} 94 | This command gets ps1 files in current directory where encoding is not ASCII 95 | 96 | .EXAMPLE 97 | Get-ChildItem *.ps1 | select FullName, @{n='Encoding';e={Get-FileEncoding $_.FullName}} | where {$_.Encoding -ne 'ASCII'} | ` 98 | foreach {(get-content $_.FullName) | set-content $_.FullName -Encoding ASCII} 99 | Same as previous example but fixes encoding using set-content 100 | #> 101 | function Get-FileEncoding 102 | { 103 | [CmdletBinding()] 104 | [OutputType([System.String])] 105 | param 106 | ( 107 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 108 | [System.String] 109 | $Path 110 | ) 111 | 112 | [System.Byte[]] $byte = Get-Content -Encoding byte -ReadCount 4 -TotalCount 4 -Path $Path 113 | 114 | if ($byte[0] -eq 0xef -and $byte[1] -eq 0xbb -and $byte[2] -eq 0xbf) 115 | { 116 | return 'UTF8' 117 | } 118 | elseif ($byte[0] -eq 0xff -and $byte[1] -eq 0xfe) 119 | { 120 | return 'UTF32' 121 | } 122 | elseif ($byte[0] -eq 0xfe -and $byte[1] -eq 0xff) 123 | { 124 | return 'BigEndianUnicode' 125 | } 126 | elseif ($byte[0] -eq 0 -and $byte[1] -eq 0 -and $byte[2] -eq 0xfe -and $byte[3] -eq 0xff) 127 | { 128 | return 'BigEndianUTF32' 129 | } 130 | else 131 | { 132 | return 'ASCII' 133 | } 134 | } 135 | 136 | Export-ModuleMember -Function ` 137 | 'Get-InvalidArgumentRecord', ` 138 | 'Get-InvalidOperationRecord', ` 139 | 'Get-FileEncoding' 140 | -------------------------------------------------------------------------------- /tests/Unit/FileContentDsc.Common.tests.ps1: -------------------------------------------------------------------------------- 1 | #region HEADER 2 | $script:projectPath = "$PSScriptRoot\..\.." | Convert-Path 3 | $script:projectName = (Get-ChildItem -Path "$script:projectPath\*\*.psd1" | Where-Object -FilterScript { 4 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 5 | $(try 6 | { 7 | Test-ModuleManifest -Path $_.FullName -ErrorAction Stop 8 | } 9 | catch 10 | { 11 | $false 12 | }) 13 | }).BaseName 14 | 15 | $script:parentModule = Get-Module -Name $script:projectName -ListAvailable | Select-Object -First 1 16 | $script:subModulesFolder = Join-Path -Path $script:parentModule.ModuleBase -ChildPath 'Modules' 17 | Remove-Module -Name $script:parentModule -Force -ErrorAction 'SilentlyContinue' 18 | 19 | $script:subModuleName = (Split-Path -Path $PSCommandPath -Leaf) -replace '\.Tests.ps1' 20 | $script:subModuleFile = Join-Path -Path $script:subModulesFolder -ChildPath "$($script:subModuleName)/$($script:subModuleName).psm1" 21 | 22 | Import-Module $script:subModuleFile -Force -ErrorAction Stop 23 | #endregion HEADER 24 | 25 | Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') 26 | 27 | InModuleScope $script:subModuleName { 28 | Describe 'FileContentDsc.Common\Get-TextEolCharacter' { 29 | $textNoNewLine = 'NoNewLine' 30 | $textCRLFOnly = "CRLFOnly`r`n" 31 | $textCROnly = "CROnly`r" 32 | $textBoth = "CRLFLine`r`nCRLine`r" 33 | 34 | Context 'text with no new line' { 35 | It 'should return CRLF' { 36 | Get-TextEolCharacter -Text $textNoNewLine | Should -Be "`r`n" 37 | } 38 | } 39 | 40 | Context 'text with CRLF only' { 41 | It 'should return CRLF' { 42 | Get-TextEolCharacter -Text $textCRLFOnly | Should -Be "`r`n" 43 | } 44 | } 45 | 46 | Context 'text with CR only' { 47 | It 'should return CR' { 48 | Get-TextEolCharacter -Text $textCROnly | Should -Be "`r" 49 | } 50 | } 51 | 52 | Context 'text with both CR and CRLF' { 53 | It 'should return CRLF' { 54 | Get-TextEolCharacter -Text $textBoth | Should -Be "`r`n" 55 | } 56 | } 57 | } 58 | 59 | Describe 'FileContentDsc.Common\Get-FileEncoding' { 60 | $testTextFile = "TestDrive:\TestFile.txt" 61 | $value = 'testText' 62 | $testCases = @( 63 | @{ 64 | encoding = 'ASCII' 65 | }, 66 | @{ 67 | encoding = 'BigEndianUnicode' 68 | }, 69 | @{ 70 | encoding = 'BigEndianUTF32' 71 | }, 72 | @{ 73 | encoding = 'UTF8' 74 | }, 75 | @{ 76 | encoding = 'UTF32' 77 | } 78 | ) 79 | 80 | Context 'When checking file encoding' { 81 | It "Should return '' for file with '' encoding" -TestCases $testCases { 82 | param 83 | ( 84 | [Parameter()] 85 | [System.String] 86 | $Encoding 87 | ) 88 | 89 | Set-Content $testTextFile -Value $value -Encoding $Encoding 90 | Get-FileEncoding -Path $testTextFile | Should -Be $Encoding 91 | } 92 | } 93 | } 94 | } 95 | --------------------------------------------------------------------------------