From ab86aa5524f1cad448dd807c53ccdddc2e55e0cc Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 28 Aug 2020 15:20:09 +0200 Subject: [PATCH] xBitlocker: Updated to a new CI/CD pipeline (#55) --- .MetaTestOptIn.json | 13 - .codecov.yml | 24 - .gitattributes | 6 + .../ISSUE_TEMPLATE/Problem_with_resource.md | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 57 +- .gitignore | 3 +- .vscode/analyzersettings.psd1 | 44 + .vscode/settings.json | 29 +- .vscode/tasks.json | 125 +++ CHANGELOG.md | 43 +- CODE_OF_CONDUCT.md | 3 + CONTRIBUTING.md | 7 + GitVersion.yml | 26 + LICENSE | 4 +- README.md | 78 +- RequiredModules.psd1 | 21 + Resolve-Dependency.ps1 | 288 ++++++ Resolve-Dependency.psd1 | 5 + Tests/Unit/xBitlockerCommon.tests.ps1 | 874 ----------------- appveyor.yml | 42 - azure-pipelines.yml | 227 +++++ build.ps1 | 381 ++++++++ build.yaml | 97 ++ codecov.yml | 44 + .../MSFT_xBLAutoBitlocker.psm1 | 18 +- .../MSFT_xBLAutoBitlocker.schema.mof | 0 .../en-US/MSFT_xBLAutoBitlocker.strings.psd1 | 2 + .../MSFT_xBLBitlocker/MSFT_xBLBitlocker.psm1 | 6 +- .../MSFT_xBLBitlocker.schema.mof | 0 .../en-US/MSFT_xBLBitlocker.strings.psd1 | 2 + .../MSFT_xBLTpm/MSFT_xBLTpm.psm1 | 16 +- .../MSFT_xBLTpm/MSFT_xBLTpm.schema.mof | 0 .../en-US/MSFT_xBLTpm.strings.psd1 | 2 + .../ConfigureBitlockerAndAutoBitlocker.ps1 | 2 - .../ConfigureBitlockerOnOSDrive.ps1 | 2 - .../en-US/xBitlocker.Common.strings.psd1 | 2 + .../xBitlocker.Common/xBitlocker.Common.psd1 | 53 ++ .../xBitlocker.Common/xBitlocker.Common.psm1 | 10 +- source/build.psd1 | 6 + source/en-US/about_xBitlocker.help.txt | 26 + source/xBitlocker.psd1 | 69 ++ ...SFT_xBLAutoBitlocker.Integration.tests.ps1 | 31 +- .../MSFT_xBLAutoBitlocker.config.ps1 | 0 .../MSFT_xBLBitlocker.Integration.tests.ps1 | 33 +- .../Integration/MSFT_xBLBitlocker.config.ps1 | 0 .../MSFT_xBLTpm.Integration.tests.ps1 | 31 +- .../Integration/MSFT_xBLTpm.config.ps1 | 0 .../TestHelpers/xBitlockerTestHelper.psm1 | 0 .../Unit/MSFT_xBLAutoBitlocker.tests.ps1 | 92 +- .../Unit/MSFT_xBLBitlocker.tests.ps1 | 44 +- {Tests => tests}/Unit/MSFT_xBLTpm.tests.ps1 | 44 +- tests/Unit/xBitlocker.Common.tests.ps1 | 877 ++++++++++++++++++ xBitlocker.psd1 | 121 --- 53 files changed, 2582 insertions(+), 1350 deletions(-) delete mode 100644 .MetaTestOptIn.json delete mode 100644 .codecov.yml create mode 100644 .vscode/analyzersettings.psd1 create mode 100644 .vscode/tasks.json create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 GitVersion.yml create mode 100644 RequiredModules.psd1 create mode 100644 Resolve-Dependency.ps1 create mode 100644 Resolve-Dependency.psd1 delete mode 100644 Tests/Unit/xBitlockerCommon.tests.ps1 delete mode 100644 appveyor.yml create mode 100644 azure-pipelines.yml create mode 100644 build.ps1 create mode 100644 build.yaml create mode 100644 codecov.yml rename {DSCResources => source/DSCResources}/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.psm1 (96%) rename {DSCResources => source/DSCResources}/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.schema.mof (100%) create mode 100644 source/DSCResources/MSFT_xBLAutoBitlocker/en-US/MSFT_xBLAutoBitlocker.strings.psd1 rename {DSCResources => source/DSCResources}/MSFT_xBLBitlocker/MSFT_xBLBitlocker.psm1 (98%) rename {DSCResources => source/DSCResources}/MSFT_xBLBitlocker/MSFT_xBLBitlocker.schema.mof (100%) create mode 100644 source/DSCResources/MSFT_xBLBitlocker/en-US/MSFT_xBLBitlocker.strings.psd1 rename {DSCResources => source/DSCResources}/MSFT_xBLTpm/MSFT_xBLTpm.psm1 (89%) rename {DSCResources => source/DSCResources}/MSFT_xBLTpm/MSFT_xBLTpm.schema.mof (100%) create mode 100644 source/DSCResources/MSFT_xBLTpm/en-US/MSFT_xBLTpm.strings.psd1 rename {Examples => source/Examples}/ConfigureBitlockerAndAutoBitlocker/ConfigureBitlockerAndAutoBitlocker.ps1 (98%) rename {Examples => source/Examples}/ConfigureBitlockerOnOSDrive/ConfigureBitlockerOnOSDrive.ps1 (97%) create mode 100644 source/Modules/xBitlocker.Common/en-US/xBitlocker.Common.strings.psd1 create mode 100644 source/Modules/xBitlocker.Common/xBitlocker.Common.psd1 rename Misc/xBitlockerCommon.psm1 => source/Modules/xBitlocker.Common/xBitlocker.Common.psm1 (98%) create mode 100644 source/build.psd1 create mode 100644 source/en-US/about_xBitlocker.help.txt create mode 100644 source/xBitlocker.psd1 rename {Tests => tests}/Integration/MSFT_xBLAutoBitlocker.Integration.tests.ps1 (71%) rename {Tests => tests}/Integration/MSFT_xBLAutoBitlocker.config.ps1 (100%) rename {Tests => tests}/Integration/MSFT_xBLBitlocker.Integration.tests.ps1 (67%) rename {Tests => tests}/Integration/MSFT_xBLBitlocker.config.ps1 (100%) rename {Tests => tests}/Integration/MSFT_xBLTpm.Integration.tests.ps1 (64%) rename {Tests => tests}/Integration/MSFT_xBLTpm.config.ps1 (100%) rename {Tests => tests}/TestHelpers/xBitlockerTestHelper.psm1 (100%) rename {Tests => tests}/Unit/MSFT_xBLAutoBitlocker.tests.ps1 (85%) rename {Tests => tests}/Unit/MSFT_xBLBitlocker.tests.ps1 (69%) rename {Tests => tests}/Unit/MSFT_xBLTpm.tests.ps1 (79%) create mode 100644 tests/Unit/xBitlocker.Common.tests.ps1 delete mode 100644 xBitlocker.psd1 diff --git a/.MetaTestOptIn.json b/.MetaTestOptIn.json deleted file mode 100644 index e31336c..0000000 --- a/.MetaTestOptIn.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - "Common Tests - Validate Markdown Files", - "Common Tests - Validate Module Files", - "Common Tests - Validate Script Files", - "Common Tests - Validate Example Files", - "Common Tests - Validate Example Files To Be Published", - "Common Tests - Required Script Analyzer Rules", - "Common Tests - New Error-Level Script Analyzer Rules", - "Common Tests - Custom Script Analyzer Rules", - "Common Tests - Flagged Script Analyzer Rules", - "Common Tests - Relative Path Length", - "Common Tests - Validate Markdown Links" -] diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index 29a05dd..0000000 --- a/.codecov.yml +++ /dev/null @@ -1,24 +0,0 @@ -codecov: - notify: - require_ci_to_pass: no - -comment: - layout: "reach, diff" - behavior: default - -coverage: - range: 50..80 - round: down - precision: 0 - - status: - project: - default: - # Set the overall project code coverage requirement to 70% - target: 70 - patch: - default: - # Set the pull request requirement to not regress overall coverage by more than 5% - # and let codecov.io set the goal for the code changed in the patch. - target: auto - threshold: 5 diff --git a/.gitattributes b/.gitattributes index 5613d53..d49b050 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,8 @@ # Needed for publishing of examples, build worker defaults to core.autocrlf=input. * text eol=crlf + +# Ensure any exe files are treated as binary +*.exe binary +*.jpg binary +*.xl* binary +*.pfx binary diff --git a/.github/ISSUE_TEMPLATE/Problem_with_resource.md b/.github/ISSUE_TEMPLATE/Problem_with_resource.md index 2431f65..159e81c 100644 --- a/.github/ISSUE_TEMPLATE/Problem_with_resource.md +++ b/.github/ISSUE_TEMPLATE/Problem_with_resource.md @@ -54,4 +54,4 @@ about: If you have a problem, bug, or enhancement with a resource in this resour $PSVersionTable --> -#### Version of the DSC module that was used ('dev' if using current dev branch) +#### Version of the DSC module that was used diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 50e18d3..8c6462e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,47 +1,52 @@ + #### Pull Request (PR) description + #### This Pull Request (PR) fixes the following issues + #### Task list + -- [ ] Added an entry under the Unreleased section of the change log in the CHANGELOG.md. - Entry should say what was changed, and how that affects users (if applicable). + +- [ ] Added an entry to the change log under the Unreleased section of the file CHANGELOG.md. + Entry should say what was changed and how that affects users (if applicable), and + reference the issue being resolved (if applicable). - [ ] Resource documentation added/updated in README.md. -- [ ] Resource parameter descriptions added/updated in README.md, schema.mof - and comment-based help. +- [ ] Resource parameter descriptions added/updated in README.md, schema.mof and comment-based + help. - [ ] Comment-based help added/updated. - [ ] Localization strings added/updated in all localization files as appropriate. - [ ] Examples appropriately added/updated. -- [ ] Unit tests added/updated. See [DSC Resource Testing Guidelines](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md). -- [ ] Integration tests added/updated (where possible). See [DSC Resource Testing Guidelines](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md). -- [ ] New/changed code adheres to [DSC Resource Style Guidelines](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md) and [Best Practices](https://github.com/PowerShell/DscResources/blob/master/BestPractices.md). +- [ ] Unit tests added/updated. See [DSC Community Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines). +- [ ] Integration tests added/updated (where possible). See [DSC Community Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines). +- [ ] New/changed code adheres to [DSC Community Style Guidelines](https://dsccommunity.org/styleguidelines). diff --git a/.gitignore b/.gitignore index c333d23..3563fb1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -DSCResource.Tests +output/ .vs .vscode -node_modules diff --git a/.vscode/analyzersettings.psd1 b/.vscode/analyzersettings.psd1 new file mode 100644 index 0000000..78312d2 --- /dev/null +++ b/.vscode/analyzersettings.psd1 @@ -0,0 +1,44 @@ +@{ + CustomRulePath = '.\output\RequiredModules\DscResource.AnalyzerRules' + includeDefaultRules = $true + IncludeRules = @( + # DSC Resource Kit style guideline rules. + 'PSAvoidDefaultValueForMandatoryParameter', + 'PSAvoidDefaultValueSwitchParameter', + 'PSAvoidInvokingEmptyMembers', + 'PSAvoidNullOrEmptyHelpMessageAttribute', + 'PSAvoidUsingCmdletAliases', + 'PSAvoidUsingComputerNameHardcoded', + 'PSAvoidUsingDeprecatedManifestFields', + 'PSAvoidUsingEmptyCatchBlock', + 'PSAvoidUsingInvokeExpression', + 'PSAvoidUsingPositionalParameters', + 'PSAvoidShouldContinueWithoutForce', + 'PSAvoidUsingWMICmdlet', + 'PSAvoidUsingWriteHost', + 'PSDSCReturnCorrectTypesForDSCFunctions', + 'PSDSCStandardDSCFunctionsInResource', + 'PSDSCUseIdenticalMandatoryParametersForDSC', + 'PSDSCUseIdenticalParametersForDSC', + 'PSMisleadingBacktick', + 'PSMissingModuleManifestField', + 'PSPossibleIncorrectComparisonWithNull', + 'PSProvideCommentHelp', + 'PSReservedCmdletChar', + 'PSReservedParams', + 'PSUseApprovedVerbs', + 'PSUseCmdletCorrectly', + 'PSUseOutputTypeCorrectly', + 'PSAvoidGlobalVars', + 'PSAvoidUsingConvertToSecureStringWithPlainText', + 'PSAvoidUsingPlainTextForPassword', + 'PSAvoidUsingUsernameAndPasswordParams', + 'PSDSCUseVerboseMessageInDSCResource', + 'PSShouldProcess', + 'PSUseDeclaredVarsMoreThanAssignments', + 'PSUsePSCredentialType', + + 'Measure-*' + ) + +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 0969e57..1bf119a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,14 +1,39 @@ { "powershell.codeFormatting.openBraceOnSameLine": false, - "powershell.codeFormatting.newLineAfterOpenBrace": false, + "powershell.codeFormatting.newLineAfterOpenBrace": true, "powershell.codeFormatting.newLineAfterCloseBrace": true, "powershell.codeFormatting.whitespaceBeforeOpenBrace": true, "powershell.codeFormatting.whitespaceBeforeOpenParen": true, "powershell.codeFormatting.whitespaceAroundOperator": true, "powershell.codeFormatting.whitespaceAfterSeparator": true, "powershell.codeFormatting.ignoreOneLineBlock": false, + "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationAfterEveryPipeline", "powershell.codeFormatting.preset": "Custom", + "powershell.codeFormatting.alignPropertyValuePairs": true, "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, - "powershell.scriptAnalysis.settingsPath": ".vscode\\analyzersettings.psd1" + "powershell.scriptAnalysis.settingsPath": ".vscode\\analyzersettings.psd1", + "powershell.scriptAnalysis.enable": true, + "files.associations": { + "*.ps1xml": "xml" + }, + "cSpell.words": [ + "COMPANYNAME", + "ICONURI", + "LICENSEURI", + "PROJECTURI", + "RELEASENOTES", + "buildhelpers", + "endregion", + "gitversion", + "icontains", + "keepachangelog", + "notin", + "pscmdlet", + "steppable" + ], + "[markdown]": { + "files.trimTrailingWhitespace": true, + "files.encoding": "utf8" + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..4085e3e --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,125 @@ +{ + "version": "2.0.0", + "_runner": "terminal", + "windows": { + "options": { + "shell": { + "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command" + ] + } + } + }, + "linux": { + "options": { + "shell": { + "executable": "/usr/bin/pwsh", + "args": [ + "-NoProfile", + "-Command" + ] + } + } + }, + "osx": { + "options": { + "shell": { + "executable": "/usr/local/bin/pwsh", + "args": [ + "-NoProfile", + "-Command" + ] + } + } + }, + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "&${cwd}/build.ps1", + "args": ["-AutoRestore"], + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "new", + "clear": false + }, + "runOptions": { + "runOn": "default" + }, + "problemMatcher": [ + { + "owner": "powershell", + "fileLocation": [ + "absolute" + ], + "severity": "error", + "pattern": [ + { + "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$", + "message": 1 + }, + { + "regexp": "(.*)", + "code": 1 + }, + { + "regexp": "" + }, + { + "regexp": "^.*,\\s*(.*):\\s*line\\s*(\\d+).*", + "file": 1, + "line": 2 + } + ] + } + ] + }, + { + "label": "test", + "type": "shell", + "command": "&${cwd}/build.ps1", + "args": ["-AutoRestore","-Tasks","test"], + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "dedicated", + "showReuseMessage": true, + "clear": false + }, + "problemMatcher": [ + { + "owner": "powershell", + "fileLocation": [ + "absolute" + ], + "severity": "error", + "pattern": [ + { + "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$", + "message": 1 + }, + { + "regexp": "(.*)", + "code": 1 + }, + { + "regexp": "" + }, + { + "regexp": "^.*,\\s*(.*):\\s*line\\s*(\\d+).*", + "file": 1, + "line": 2 + } + ] + } + ] + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index ad60904..a6e436e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,21 @@ # Change log for xBitlocker -## Unreleased +The format is based on and uses the types of changes according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Updated to a new CI/CD pipeline ([issue #54)](https://github.com/dsccommunity/xBitlocker/issues/54). + +### Fixed - Fix hashtables according to the style guideline. -## 1.4.0.0 +## [1.4.0.0] - 2019-01-09 + +### Changed - Change double quoted string literals to single quotes - Add spaces between array members @@ -12,7 +23,9 @@ - Add spaces between comment hashtag and comments - Explicitly removed extra hidden files from release package -## 1.3.0.0 +## [1.3.0.0] - 2018-11-28 + +### Changed - Update appveyor.yml to use the default template. - Added default template files .gitattributes, and .vscode settings. @@ -33,7 +46,9 @@ - Fixes issue where Switch parameters are passed to Enable-Bitlocker even if the corresponding DSC resource parameter was set to False (Issue #12) -## 1.2.0.0 +## [1.2.0.0] - 2018-06-13 + +### Changed - Converted appveyor.yml to install Pester from PSGallery instead of from Chocolatey. @@ -48,23 +63,27 @@ module manifest. - Fixed issue which caused Test to incorrectly succeed on fully decrypted volumes when correct Key Protectors were present - ([issue #13](https://github.com/PowerShell/xBitlocker/issues/13)) + ([issue #13](https://github.com/dsccommunity/xBitlocker/issues/13)) - Fixed issue which caused xBLAutoBitlocker to incorrectly detect Fixed vs Removable volumes. - ([issue #11](https://github.com/PowerShell/xBitlocker/issues/11)) + ([issue #11](https://github.com/dsccommunity/xBitlocker/issues/11)) - Fixed issue which made xBLAutoBitlocker unable to encrypt volumes with drive letters assigned. - ([issue #10](https://github.com/PowerShell/xBitlocker/issues/10)) + ([issue #10](https://github.com/dsccommunity/xBitlocker/issues/10)) - Fixed an issue in CheckForPreReqs function where on Server Core the installation of the non existing Windows Feature 'RSAT-Feature-Tools-BitLocker-RemoteAdminTool' was erroneously checked. - ([issue #8](https://github.com/PowerShell/xBitlocker/issues/8)) + ([issue #8](https://github.com/dsccommunity/xBitlocker/issues/8)) + +## [1.1.0.0] - 2016-02-02 -## 1.1.0.0 +### Changed - Versioning updates -## 1.0.1.1 +## [1.0.1.1] - 2015-04-23 + +### Changed - Reduced the number of acceptable values for PrimaryProtector in xBLAutoBitlocker and xBLBitlocker. @@ -72,7 +91,9 @@ xBLAutoBitlocker, xBLBitlocker, and xBLTpm. - Fixed issue which caused protectors to be continually re-added. -## 1.0.0.0 +## [1.0.0.0] - 2015-04-15 + +### Changed - Initial release with the following resources - xBLAutoBitlocker diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d7589dd --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +This project has adopted the [DSC Community Code of Conduct](https://dsccommunity.org/code_of_conduct). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3544bcc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +# Contributing + +Please check out common DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing). + +## Running the Tests + +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) diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..9a735f2 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,26 @@ +mode: ContinuousDelivery +next-version: 1.5.0 +major-version-bump-message: '(breaking\schange|breaking|major)\b' +minor-version-bump-message: '(adds?|features?|minor)\b' +patch-version-bump-message: '\s?(fix|patch)' +no-bump-message: '\+semver:\s?(none|skip)' +assembly-informational-format: '{NuGetVersionV2}+Sha.{Sha}.Date.{CommitDate}' +branches: + master: + tag: preview + pull-request: + tag: PR + feature: + tag: useBranchName + increment: Minor + regex: f(eature(s)?)?[\/-] + source-branches: ['master'] + hotfix: + tag: fix + increment: Patch + regex: (hot)?fix(es)?[\/-] + source-branches: ['master'] + +ignore: + sha: [] +merge-message-formats: {} diff --git a/LICENSE b/LICENSE index 567fd6a..f4c3ee9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Microsoft Corporation. +Copyright (c) DSC Community contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. diff --git a/README.md b/README.md index 9c338ef..310dfc7 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,28 @@ # xBitlocker -The **xBitlocker** module is a part of the Windows PowerShell Desired State -Configuration (DSC) Resource Kit, which is a collection of DSC Resources -produced by the PowerShell Team. -This module contains the **xBLAutoBitlocker, xBLBitlocker, xBLTpm** resources. -This DSC Module allows you to configure Bitlocker on a single disk, configure a -TPM chip, or automatically enable Bitlocker on multiple disks. - -This project has adopted the -[Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) -. For more information see the -[Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any -additional questions or comments. +[![Build Status](https://dev.azure.com/dsccommunity/xBitlocker/_apis/build/status/dsccommunity.xBitlocker?branchName=master)](https://dev.azure.com/dsccommunity/xBitlocker/_build/latest?definitionId=46&branchName=master) +![Azure DevOps coverage (branch)](https://img.shields.io/azure-devops/coverage/dsccommunity/xBitlocker/46/master) +[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/dsccommunity/xBitlocker/46/master)](https://dsccommunity.visualstudio.com/xBitlocker/_test/analytics?definitionId=46&contextType=build) +[![PowerShell Gallery (with prereleases)](https://img.shields.io/powershellgallery/vpre/xBitlocker?label=xBitlocker%20Preview)](https://www.powershellgallery.com/packages/xBitlocker/) +[![PowerShell Gallery](https://img.shields.io/powershellgallery/v/xBitlocker?label=xBitlocker)](https://www.powershellgallery.com/packages/xBitlocker/) -## Branches - -### master - -[![Build status](https://ci.appveyor.com/api/projects/status/wi5i60tojfd7056b/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/xBitlocker/branch/master) -[![codecov](https://codecov.io/gh/PowerShell/xBitlocker/branch/master/graph/badge.svg)](https://codecov.io/gh/PowerShell/xBitlocker/branch/master) +This DSC module allows you to configure Bitlocker on a single disk, configure a +TPM chip, or automatically enable Bitlocker on multiple disks. -This is the branch containing the latest release - -no contributions should be made directly to this branch. +## Code of Conduct -### dev +This project has adopted this [Code of Conduct](CODE_OF_CONDUCT.md). -[![Build status](https://ci.appveyor.com/api/projects/status/wi5i60tojfd7056b/branch/dev?svg=true)](https://ci.appveyor.com/project/PowerShell/xBitlocker/branch/dev) -[![codecov](https://codecov.io/gh/PowerShell/xBitlocker/branch/dev/graph/badge.svg)](https://codecov.io/gh/PowerShell/xBitlocker/branch/dev) +## Releases -This is the development branch -to which contributions should be proposed by contributors as pull requests. -This development branch will periodically be merged to the master branch, -and be released to [PowerShell Gallery](https://www.powershellgallery.com/). +For each merge to the branch `master` a preview release will be +deployed to [PowerShell Gallery](https://www.powershellgallery.com/). +Periodically a release version tag will be pushed which will deploy a +full release to [PowerShell Gallery](https://www.powershellgallery.com/). ## Contributing -Please check out common DSC Resources -[contributing guidelines](https://github.com/PowerShell/DscResource.Kit/blob/master/CONTRIBUTING.md). - -## Installation - -To install **xBitlocker** module - -* Unzip the content under $env:ProgramFiles\WindowsPowerShell\Modules folder - -To confirm installation: - -* Run **Get-DSCResource** to see that **xBLAutoBitlocker**, **xBLBitlocker**, - **xBLTpm** are among the DSC Resources listed. +Please check out common DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing). ## Requirements @@ -56,10 +30,8 @@ This module requires that both the **Bitlocker** and **RSAT-Feature-Tools-Bitlocker** features are installed. It also requires the latest version of PowerShell (v4.0, which ships in Windows 8.1 or Windows Server 2012R2). -To easily use PowerShell 4.0 on older operating systems, -[Install WMF 4.0](http://www.microsoft.com/en-us/download/details.aspx?id=40855) -. Please read the installation instructions that are present on both the -download page and the release notes for WMF 4.0. +For more information on using PowerShell 4.0 on older operating systems, +[Install WMF 4.0](https://social.technet.microsoft.com/wiki/contents/articles/21016.how-to-install-windows-powershell-4-0.aspx). ## Description @@ -138,9 +110,7 @@ parameters. * TpmProtector * UsedSpaceOnly -**xBLTpm** is used to initialize a TPM chip using -[Initialize-TPM](https://docs.microsoft.com/en-us/powershell/module/trustedplatformmodule/initialize-tpm) -. +**xBLTpm** is used to initialize a TPM chip using [Initialize-TPM](https://docs.microsoft.com/en-us/powershell/module/trustedplatformmodule/initialize-tpm). **xBLTpm** has the following properties. * Identity: A required string value which is used as a Key for the resource. @@ -156,17 +126,15 @@ parameters. ## Examples -### [ConfigureBitlockerOnOSDrive](Examples/ConfigureBitlockerOnOSDrive) +### [ConfigureBitlockerOnOSDrive](source/Examples/ConfigureBitlockerOnOSDrive) - This example enables Bitlocker on an Operating System drive. +This example enables Bitlocker on an Operating System drive. The example code for ConfigureBitlockerOnOSDrive is located in -[`ConfigureBitlockerOnOSDrive.ps1`](Examples/ConfigureBitlockerOnOSDrive/ConfigureBitlockerOnOSDrive.ps1) -. +[`ConfigureBitlockerOnOSDrive.ps1`](source/Examples/ConfigureBitlockerOnOSDrive/ConfigureBitlockerOnOSDrive.ps1). -### [ConfigureBitlockerAndAutoBitlocker](Examples/ConfigureBitlockerAndAutoBitlocker) +### [ConfigureBitlockerAndAutoBitlocker](source/Examples/ConfigureBitlockerAndAutoBitlocker) Enables Bitlocker on an Operating System drive, and automatically enables Bitlocker on all drives of type 'Fixed'. The example code for ConfigureBitlockerAndAutoBitlocker is located in -[`ConfigureBitlockerAndAutoBitlocker.ps1`](Examples/ConfigureBitlockerAndAutoBitlocker/ConfigureBitlockerAndAutoBitlocker.ps1) -. +[`ConfigureBitlockerAndAutoBitlocker.ps1`](source/Examples/ConfigureBitlockerAndAutoBitlocker/ConfigureBitlockerAndAutoBitlocker.ps1). diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 new file mode 100644 index 0000000..f5ea8cf --- /dev/null +++ b/RequiredModules.psd1 @@ -0,0 +1,21 @@ +@{ + PSDependOptions = @{ + AddToPath = $true + Target = 'output\RequiredModules' + Parameters = @{ + Repository = '' + } + } + + InvokeBuild = 'latest' + PSScriptAnalyzer = 'latest' + Pester = '4.10.1' + Plaster = 'latest' + ModuleBuilder = 'latest' + ChangelogManagement = 'latest' + Sampler = 'latest' + MarkdownLinkCheck = 'latest' + 'DscResource.Test' = 'latest' + 'DscResource.AnalyzerRules' = 'latest' + xDscResourceDesigner = 'latest' +} diff --git a/Resolve-Dependency.ps1 b/Resolve-Dependency.ps1 new file mode 100644 index 0000000..9541f73 --- /dev/null +++ b/Resolve-Dependency.ps1 @@ -0,0 +1,288 @@ +[CmdletBinding()] +param +( + [Parameter()] + [String] + $DependencyFile = 'RequiredModules.psd1', + + [Parameter()] + [String] + # Path for PSDepend to be bootstrapped and save other dependencies. + # Can also be CurrentUser or AllUsers if you wish to install the modules in such scope + # Default to $PWD.Path/output/modules + $PSDependTarget = (Join-Path $PSScriptRoot './output/RequiredModules'), + + [Parameter()] + [uri] + # URI to use for Proxy when attempting to Bootstrap PackageProvider & PowerShellGet + $Proxy, + + [Parameter()] + # Credential to contact the Proxy when provided + [PSCredential]$ProxyCredential, + + [Parameter()] + [ValidateSet('CurrentUser', 'AllUsers')] + [String] + # Scope to bootstrap the PackageProvider and PSGet if not available + $Scope = 'CurrentUser', + + [Parameter()] + [String] + # Gallery to use when bootstrapping PackageProvider, PSGet and when calling PSDepend (can be overridden in Dependency files) + $Gallery = 'PSGallery', + + [Parameter()] + [PSCredential] + # Credentials to use with the Gallery specified above + $GalleryCredential, + + + [Parameter()] + [switch] + # Allow you to use a locally installed version of PowerShellGet older than 1.6.0 (not recommended, default to $False) + $AllowOldPowerShellGetModule, + + [Parameter()] + [String] + # Allow you to specify a minimum version fo PSDepend, if you're after specific features. + $MinimumPSDependVersion, + + [Parameter()] + [Switch] + $AllowPrerelease, + + [Parameter()] + [Switch] + $WithYAML +) + +# Load Defaults for parameters values from Resolve-Dependency.psd1 if not provided as parameter +try +{ + Write-Verbose -Message "Importing Bootstrap default parameters from '$PSScriptRoot/Resolve-Dependency.psd1'." + $ResolveDependencyDefaults = Import-PowerShellDataFile -Path (Join-Path $PSScriptRoot '.\Resolve-Dependency.psd1' -Resolve -ErrorAction Stop) + $ParameterToDefault = $MyInvocation.MyCommand.ParameterSets.Where{ $_.Name -eq $PSCmdlet.ParameterSetName }.Parameters.Keys + if ($ParameterToDefault.Count -eq 0) + { + $ParameterToDefault = $MyInvocation.MyCommand.Parameters.Keys + } + # Set the parameters available in the Parameter Set, or it's not possible to choose yet, so all parameters are an option + foreach ($ParamName in $ParameterToDefault) + { + if (-Not $PSBoundParameters.Keys.Contains($ParamName) -and $ResolveDependencyDefaults.ContainsKey($ParamName)) + { + Write-Verbose -Message "Setting $ParamName with $($ResolveDependencyDefaults[$ParamName])" + try + { + $variableValue = $ResolveDependencyDefaults[$ParamName] + if ($variableValue -is [string]) + { + $variableValue = $ExecutionContext.InvokeCommand.ExpandString($variableValue) + } + $PSBoundParameters.Add($ParamName, $variableValue) + Set-Variable -Name $ParamName -value $variableValue -Force -ErrorAction SilentlyContinue + } + catch + { + Write-Verbose -Message "Error adding default for $ParamName : $($_.Exception.Message)" + } + } + } +} +catch +{ + Write-Warning -Message "Error attempting to import Bootstrap's default parameters from $(Join-Path $PSScriptRoot '.\Resolve-Dependency.psd1'): $($_.Exception.Message)." +} + +Write-Progress -Activity "Bootstrap:" -PercentComplete 0 -CurrentOperation "NuGet Bootstrap" + +if (!(Get-PackageProvider -Name NuGet -ForceBootstrap -ErrorAction SilentlyContinue)) +{ + $providerBootstrapParams = @{ + Name = 'nuget' + force = $true + ForceBootstrap = $true + ErrorAction = 'Stop' + } + + switch ($PSBoundParameters.Keys) + { + 'Proxy' + { + $providerBootstrapParams.Add('Proxy', $Proxy) + } + 'ProxyCredential' + { + $providerBootstrapParams.Add('ProxyCredential', $ProxyCredential) + } + 'Scope' + { + $providerBootstrapParams.Add('Scope', $Scope) + } + } + + if ($AllowPrerelease) + { + $providerBootstrapParams.Add('AllowPrerelease', $true) + } + + Write-Information "Bootstrap: Installing NuGet Package Provider from the web (Make sure Microsoft addresses/ranges are allowed)" + $null = Install-PackageProvider @providerBootstrapParams + $latestNuGetVersion = (Get-PackageProvider -Name NuGet -ListAvailable | Select-Object -First 1).Version.ToString() + Write-Information "Bootstrap: Importing NuGet Package Provider version $latestNuGetVersion to current session." + $Null = Import-PackageProvider -Name NuGet -RequiredVersion $latestNuGetVersion -Force +} + +Write-Progress -Activity "Bootstrap:" -PercentComplete 10 -CurrentOperation "Ensuring Gallery $Gallery is trusted" + +# Fail if the given PSGallery is not Registered +$Policy = (Get-PSRepository $Gallery -ErrorAction Stop).InstallationPolicy +Set-PSRepository -Name $Gallery -InstallationPolicy Trusted -ErrorAction Ignore +try +{ + Write-Progress -Activity "Bootstrap:" -PercentComplete 25 -CurrentOperation "Checking PowerShellGet" + # Ensure the module is loaded and retrieve the version you have + $PowerShellGetVersion = (Import-Module PowerShellGet -PassThru -ErrorAction SilentlyContinue).Version + + Write-Verbose "Bootstrap: The PowerShellGet version is $PowerShellGetVersion" + # Versions below 1.6.0 are considered old, unreliable & not recommended + if (!$PowerShellGetVersion -or ($PowerShellGetVersion -lt [System.version]'1.6.0' -and !$AllowOldPowerShellGetModule)) + { + Write-Progress -Activity "Bootstrap:" -PercentComplete 40 -CurrentOperation "Installing newer version of PowerShellGet" + $InstallPSGetParam = @{ + Name = 'PowerShellGet' + Force = $True + SkipPublisherCheck = $true + AllowClobber = $true + Scope = $Scope + Repository = $Gallery + } + + switch ($PSBoundParameters.Keys) + { + 'Proxy' + { + $InstallPSGetParam.Add('Proxy', $Proxy) + } + 'ProxyCredential' + { + $InstallPSGetParam.Add('ProxyCredential', $ProxyCredential) + } + 'GalleryCredential' + { + $InstallPSGetParam.Add('Credential', $GalleryCredential) + } + } + + Install-Module @InstallPSGetParam + Remove-Module PowerShellGet -force -ErrorAction SilentlyContinue + Import-Module PowerShellGet -Force + $NewLoadedVersion = (Get-Module PowerShellGet).Version.ToString() + Write-Information "Bootstrap: PowerShellGet version loaded is $NewLoadedVersion" + Write-Progress -Activity "Bootstrap:" -PercentComplete 60 -CurrentOperation "Installing newer version of PowerShellGet" + } + + # Try to import the PSDepend module from the available modules + try + { + $ImportPSDependParam = @{ + Name = 'PSDepend' + ErrorAction = 'Stop' + Force = $true + } + + if ($MinimumPSDependVersion) + { + $ImportPSDependParam.add('MinimumVersion', $MinimumPSDependVersion) + } + $null = Import-Module @ImportPSDependParam + } + catch + { + # PSDepend module not found, installing or saving it + if ($PSDependTarget -in 'CurrentUser', 'AllUsers') + { + Write-Debug "PSDepend module not found. Attempting to install from Gallery $Gallery" + Write-Warning "Installing PSDepend in $PSDependTarget Scope" + $InstallPSDependParam = @{ + Name = 'PSDepend' + Repository = $Gallery + Force = $true + Scope = $PSDependTarget + SkipPublisherCheck = $true + AllowClobber = $true + } + + if ($MinimumPSDependVersion) + { + $InstallPSDependParam.add('MinimumVersion', $MinimumPSDependVersion) + } + + Write-Progress -Activity "Bootstrap:" -PercentComplete 75 -CurrentOperation "Installing PSDepend from $Gallery" + Install-Module @InstallPSDependParam + } + else + { + Write-Debug "PSDepend module not found. Attempting to Save from Gallery $Gallery to $PSDependTarget" + $SaveModuleParam = @{ + Name = 'PSDepend' + Repository = $Gallery + Path = $PSDependTarget + } + + if ($MinimumPSDependVersion) + { + $SaveModuleParam.add('MinimumVersion', $MinimumPSDependVersion) + } + + Write-Progress -Activity "Bootstrap:" -PercentComplete 75 -CurrentOperation "Saving & Importing PSDepend from $Gallery to $Scope" + Save-Module @SaveModuleParam + } + } + finally + { + Write-Progress -Activity "Bootstrap:" -PercentComplete 100 -CurrentOperation "Loading PSDepend" + # We should have successfully bootstrapped PSDepend. Fail if not available + Import-Module PSDepend -ErrorAction Stop + } + + if ($WithYAML) + { + if (-Not (Get-Module -ListAvailable -Name 'PowerShell-Yaml')) + { + Write-Verbose "PowerShell-Yaml module not found. Attempting to Save from Gallery $Gallery to $PSDependTarget" + $SaveModuleParam = @{ + Name = 'PowerShell-Yaml' + Repository = $Gallery + Path = $PSDependTarget + } + + Save-Module @SaveModuleParam + Import-Module "PowerShell-Yaml" -ErrorAction Stop + } + else + { + Write-Verbose "PowerShell-Yaml is already available" + } + } + + Write-Progress -Activity "PSDepend:" -PercentComplete 0 -CurrentOperation "Restoring Build Dependencies" + if (Test-Path $DependencyFile) + { + $PSDependParams = @{ + Force = $true + Path = $DependencyFile + } + + # TODO: Handle when the Dependency file is in YAML, and -WithYAML is specified + Invoke-PSDepend @PSDependParams + } + Write-Progress -Activity "PSDepend:" -PercentComplete 100 -CurrentOperation "Dependencies restored" -Completed +} +finally +{ + # Reverting the Installation Policy for the given gallery + Set-PSRepository -Name $Gallery -InstallationPolicy $Policy + Write-Verbose "Project Bootstrapped, returning to Invoke-Build" +} diff --git a/Resolve-Dependency.psd1 b/Resolve-Dependency.psd1 new file mode 100644 index 0000000..2ae8c0d --- /dev/null +++ b/Resolve-Dependency.psd1 @@ -0,0 +1,5 @@ +@{ + Gallery = 'PSGallery' + AllowPrerelease = $false + WithYAML = $true +} diff --git a/Tests/Unit/xBitlockerCommon.tests.ps1 b/Tests/Unit/xBitlockerCommon.tests.ps1 deleted file mode 100644 index e52cd6f..0000000 --- a/Tests/Unit/xBitlockerCommon.tests.ps1 +++ /dev/null @@ -1,874 +0,0 @@ -$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'Misc' -ChildPath 'xBitlockerCommon.psm1')) -Force - -# Begin Testing -try -{ - InModuleScope 'xBitlockerCommon' { - - function Get-BitlockerVolume - { - param - ( - [Parameter()] - [System.String] - $MountPoint - ) - } - - Describe 'xBitlockerCommon\Test-BitlockerEnabled' { - - Context 'When OS Volume is not Encrypted and No Key Protectors Assigned' { - Mock ` - -CommandName Get-BitlockerVolume ` - -ModuleName 'xBitlockerCommon' ` - -MockWith { - # Decrypted with no Key Protectors - return @{ - VolumeType = 'OperatingSystem' - MountPoint = $MountPoint - CapacityGB = 500 - VolumeStatus = 'FullyDecrypted' - EncryptionPercentage = 0 - KeyProtector = @() - AutoUnlockEnabled = $null - ProtectionStatus = 'Off' - } - } - - It 'Should Fail The Test (TPM and RecoveryPassword Protectors)' { - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TPMProtector' -RecoveryPasswordProtector $true | Should -Be $false - } - } - - Context 'When OS Volume is Encrypted using TPM and Recovery Password Protectors' { - Mock ` - -CommandName Get-BitlockerVolume ` - -ModuleName 'xBitlockerCommon' ` - -MockWith { - # Encrypted with TPM and Recovery Password Key Protectors - return @{ - VolumeType = 'OperatingSystem' - MountPoint = $MountPoint - CapacityGB = 500 - VolumeStatus = 'FullyEncrypted' - EncryptionPercentage = 100 - KeyProtector = @( - @{ - KeyProtectorType = 'Tpm' - }, - @{ - KeyProtectorType = 'RecoveryPassword' - } - ) - AutoUnlockEnabled = $null - ProtectionStatus = 'On' - } - } - - It 'Should Pass The Test (TPM and RecoveryPassword Protectors)' { - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TPMProtector' -RecoveryPasswordProtector $true -verbose | Should -Be $true - } - } - - Context 'When OS Volume is Decrypted, but has TPM and Recovery Password Protectors assigned' { - Mock ` - -CommandName Get-BitlockerVolume ` - -ModuleName 'xBitlockerCommon' ` - -MockWith { - # Encrypted with TPM and Recovery Password Key Protectors - return @{ - VolumeType = 'OperatingSystem' - MountPoint = $MountPoint - CapacityGB = 500 - VolumeStatus = 'FullyDecrypted' - EncryptionPercentage = 0 - KeyProtector = @( - @{ - KeyProtectorType = 'Tpm' - }, - @{ - KeyProtectorType = 'RecoveryPassword' - } - ) - AutoUnlockEnabled = $null - ProtectionStatus = 'Off' - } - } - - It 'Should Fail The Test (TPM and RecoveryPassword Protectors)' { - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TPMProtector' -RecoveryPasswordProtector $true | Should -Be $false - } - } - - Context 'When Test-BitlockerEnabled is called and Get-BitlockerVolume returns null' { - It 'Should return False' { - Mock -CommandName Get-BitLockerVolume -Verifiable - - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' | Should -Be $false - } - } - - Context 'When Test-BitlockerEnabled is called and Get-BitlockerVolume returns a volume with no key protectors' { - It 'Should return False' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { - return @{ - KeyProtector = $null - } - } - - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' | Should -Be $false - } - } - - Context 'When Test-BitlockerEnabled is called, AutoUnlock is requested on a non-OS disk, and AutoUnlock is not enabled' { - It 'Should return False' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { - return @{ - AutoUnlockEnabled = $false - VolumeType = 'Data' - KeyProtector = @('Protector1') - } - } - - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -AutoUnlock $true | Should -Be $false - } - } - - $defaultBLV = @( - @{ - KeyProtector = @('Protector1') - } - ) - - $fakePin = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'fakepin', (New-Object -TypeName System.Security.SecureString) - - Context 'When Test-BitlockerEnabled is called, a AdAccountOrGroupProtector protector is requested, and does not exist on the disk' { - It 'Should return False' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } - - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -AdAccountOrGroupProtector $true | Should -Be $false - } - } - - Context 'When Test-BitlockerEnabled is called, a PasswordProtector protector is requested, and does not exist on the disk' { - It 'Should return False' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } - - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -PasswordProtector $true | Should -Be $false - } - } - - Context 'When Test-BitlockerEnabled is called, a Pin protector is requested, and does not exist on the disk' { - It 'Should return False' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } - - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -Pin $fakePin | Should -Be $false - } - } - - Context 'When Test-BitlockerEnabled is called, a RecoveryKeyProtector protector is requested, and does not exist on the disk' { - It 'Should return False' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } - - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -RecoveryKeyProtector $true | Should -Be $false - } - } - - Context 'When Test-BitlockerEnabled is called, a RecoveryPasswordProtector protector is requested, and does not exist on the disk' { - It 'Should return False' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } - - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -RecoveryPasswordProtector $true | Should -Be $false - } - } - - Context 'When Test-BitlockerEnabled is called, a StartupKeyProtector protector is requested without a primary TPM protector, and does not exist on the disk' { - It 'Should return False' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } - - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'StartupKeyProtector' -StartupKeyProtector $true | Should -Be $false - } - } - - Context 'When Test-BitlockerEnabled is called, a StartupKeyProtector protector is requested with a primary TPM protector, and does not exist on the disk' { - It 'Should return False' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } - - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -StartupKeyProtector $true | Should -Be $false - } - } - - Context 'When Test-BitlockerEnabled is called, a TpmProtector protector is requested, and does not exist on the disk' { - It 'Should return False' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } - - Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -TpmProtector $true | Should -Be $false - } - } - } - - Describe 'xBitlockerCommon\Assert-HasPrereqsForBitlocker' { - function Get-WindowsFeature - { - param - ( - [string] - $FeatureName - ) - } - - function Get-OSEdition - { - - } - - Context 'When OS is Server Core and all required features are installed' { - Mock -CommandName Get-OSEdition -MockWith { - 'Server Core' - } - - Mock -CommandName Get-WindowsFeature -MockWith { - if ($FeatureName -eq 'RSAT-Feature-Tools-BitLocker-RemoteAdminTool') - { - return $null - } - else - { - return @{ - DisplayName = $FeatureName - Name = $FeatureName - InstallState = 'Installed' - } - } - } - - It 'Should not generate any error messages' { - Mock -CommandName Write-Error - Assert-HasPrereqsForBitlocker - Assert-MockCalled -Command Write-Error -Exactly -Times 0 -Scope It - } - - It 'Should run the Assert-HasPrereqsForBitlocker function without exceptions' { - {Assert-HasPrereqsForBitlocker} | Should -Not -Throw - } - } - - Context 'When OS is Full Server and all required features are installed' { - Mock -CommandName Get-OSEdition -MockWith { - return 'Server' - } - - Mock -CommandName Get-WindowsFeature -MockWith { - param - ( - [string] - $FeatureName - ) - - return @{ - DisplayName = $FeatureName - Name = $FeatureName - InstallState = 'Installed' - } - } - - It 'Should not generate any error messages' { - Mock -CommandName Write-Error - Assert-HasPrereqsForBitlocker - Assert-MockCalled -Command Write-Error -Exactly -Times 0 -Scope It - } - - It 'Should run the Assert-HasPrereqsForBitlocker function without exceptions' { - {Assert-HasPrereqsForBitlocker} | Should -Not -Throw - } - } - - Context 'When OS is Full Server without the required features installed' { - Mock -CommandName Get-OSEdition -MockWith { - return 'Server' - } - - Mock -CommandName Get-WindowsFeature -MockWith { - return @{ - DisplayName = $FeatureName - Name = $FeatureName - InstallState = 'Available' - } - } - - Mock -CommandName Write-Error - - It 'Should give an error that Bitlocker Windows Feature needs to be installed' { - {Assert-HasPrereqsForBitlocker} | Should -Throw - Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter { - $Message -eq 'The Bitlocker feature needs to be installed before the xBitlocker module can be used' - } - } - - It 'Should give an error that RSAT-Feature-Tools-BitLocker Windows Feature needs to be installed' { - {Assert-HasPrereqsForBitlocker} | Should -Throw - Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter { - $Message -eq 'The RSAT-Feature-Tools-BitLocker feature needs to be installed before the xBitlocker module can be used' - } - } - - It 'Should give an error that RSAT-Feature-Tools-BitLocker-RemoteAdminTool Windows Feature needs to be installed' { - {Assert-HasPrereqsForBitlocker} | Should -Throw - Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter { - $Message -eq 'The RSAT-Feature-Tools-BitLocker-RemoteAdminTool feature needs to be installed before the xBitlocker module can be used' - } - } - - It 'The Assert-HasPrereqsForBitlocker function should throw an exceptions about missing required Windows Features' { - {Assert-HasPrereqsForBitlocker} | Should -Throw 'Required Bitlocker features need to be installed before xBitlocker can be used' - } - } - - Context 'When OS is Server Core without the required features installed' { - Mock -CommandName Get-OSEdition -MockWith { - return 'Server Core' - } - - Mock -CommandName Get-WindowsFeature -MockWith { - param - ( - [string] - $FeatureName - ) - - if ($FeatureName -eq 'RSAT-Feature-Tools-BitLocker-RemoteAdminTool') - { - return $null - } - else - { - - return @{ - DisplayName = $FeatureName - Name = $FeatureName - InstallState = 'Available' - } - } - } - - Mock -CommandName Write-Error - - It 'Should give an error that Bitlocker Windows Feature needs to be installed' { - {Assert-HasPrereqsForBitlocker} | Should -Throw - Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter { - $Message -eq 'The Bitlocker feature needs to be installed before the xBitlocker module can be used' - } - } - - It 'Should give an error that RSAT-Feature-Tools-BitLocker Windows Feature needs to be installed' { - {Assert-HasPrereqsForBitlocker} | Should -Throw - Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter { - $Message -eq 'The RSAT-Feature-Tools-BitLocker feature needs to be installed before the xBitlocker module can be used' - } - } - - It 'Should not give an error that RSAT-Feature-Tools-BitLocker-RemoteAdminTool Windows Feature needs to be installed as this Windows Features is not available on Server Core.' { - {Assert-HasPrereqsForBitlocker} | Should -Throw - Assert-MockCalled -Command Write-Error -Exactly -Times 0 -Scope It -ParameterFilter { - $Message -eq 'The RSAT-Feature-Tools-BitLocker-RemoteAdminTool feature needs to be installed before the xBitlocker module can be used' - } - } - - It 'The Assert-HasPrereqsForBitlocker function should throw an exceptions about missing required Windows Features' { - {Assert-HasPrereqsForBitlocker} | Should -Throw 'Required Bitlocker features need to be installed before xBitlocker can be used' - } - } - } - - Describe 'xBitLockerCommon\Get-OSEdition' { - It 'Should return "Server Core" if the OS is Windows Server Core' { - Mock -CommandName Get-ItemProperty -MockWith { - [PSCustomObject] @{ - InstallationType = 'Server Core' - PSPath = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt\currentversion' - PSParentPath = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt' - PSChildName = 'currentversion' - PSDrive = 'HKLM' - PSProvider = 'Microsoft.PowerShell.Core\Registry' - } - } - - $OSVersion = Get-OSEdition - $OSVersion | Should -Be 'Server Core' - } - - It 'Should return "Server" if the OS is Full Windows Server' { - Mock -CommandName Get-ItemProperty -MockWith { - [PSCustomObject] @{ - InstallationType = 'Server' - PSPath = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt\currentversion' - PSParentPath = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt' - PSChildName = 'currentversion' - PSDrive = 'HKLM' - PSProvider = 'Microsoft.PowerShell.Core\Registry' - } - } - - $OSVersion = Get-OSEdition - $OSVersion | Should -Be 'Server' - } - - It 'Should run without exceptions' { - Mock -CommandName Get-ItemProperty -MockWith { - [PSCustomObject] @{ - InstallationType = 'Some other os' - PSPath = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt\currentversion' - PSParentPath = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt' - PSChildName = 'currentversion' - PSDrive = 'HKLM' - PSProvider = 'Microsoft.PowerShell.Core\Registry' - } - } - {Get-OSEdition} | Should -Not -Throw - } - } - - Describe 'xBitLockerCommon\Enable-BitlockerInternal' -Tag 'Helper' { - # Override Bitlocker cmdlets - function Enable-Bitlocker {} - function Enable-BitlockerAutoUnlock {} - - AfterEach { - Assert-VerifiableMock - } - - $fakePin = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'fakepin', (New-Object -TypeName System.Security.SecureString) - $mountPoint = 'C:' - $encryptedBLV = @{ - VolumeStatus = 'FullyEncrypted' - } - $encryptedOSBLV = @{ - VolumeStatus = 'FullyEncrypted' - VolumeType = 'OperatingSystem' - } - $decryptedOSBLV = @{ - VolumeStatus = 'FullyDecrypted' - VolumeType = 'OperatingSystem' - } - - Context 'When Enable-BitlockerInternal is called Get-BitlockerVolume returns null' { - It 'Should throw an exception' { - Mock -CommandName Get-BitLockerVolume -Verifiable - - { Enable-BitlockerInternal -MountPoint 'C:' -Pin $fakePin -PrimaryProtector 'PasswordProtector' } | Should -Throw -ExpectedMessage 'Unable to find Bitlocker Volume associated with Mount Point' - } - } - - Context 'When Enable-BitlockerInternal is called with TpmProtector set to True and PrimaryProtector not set to TpmProtector' { - $badPrimaryProtectorCases = @( - @{ - PrimaryProtector = 'PasswordProtector' - } - - @{ - PrimaryProtector = 'RecoveryPasswordProtector' - } - - @{ - PrimaryProtector = 'StartupKeyProtector' - } - ) - - It 'Should throw an exception' -TestCases $badPrimaryProtectorCases { - param - ( - [System.String] - $PrimaryProtector - ) - - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } - - { Enable-BitlockerInternal -MountPoint $mountPoint -TpmProtector $true -PrimaryProtector $PrimaryProtector } | Should -Throw -ExpectedMessage 'If TpmProtector is used, it must be the PrimaryProtector.' - } - } - - Context 'When Enable-BitlockerInternal is called with Pin specified and TpmProtector not specified' { - It 'Should throw an exception' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } - - { Enable-BitlockerInternal -MountPoint $mountPoint -Pin $fakePin -PrimaryProtector 'PasswordProtector' } | Should -Throw -ExpectedMessage 'A TpmProtector must be used if Pin is used.' - } - } - - Context 'When Enable-BitlockerInternal is called with Pin specified and TpmProtector not specified' { - It 'Should throw an exception' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } - - { Enable-BitlockerInternal -MountPoint $mountPoint -Pin $fakePin -PrimaryProtector 'PasswordProtector' } | Should -Throw -ExpectedMessage 'A TpmProtector must be used if Pin is used.' - } - } - - $defaultEnableParams = @{ - MountPoint = $mountPoint - Pin = $fakePin - PrimaryProtector = 'TpmProtector' - TpmProtector = $true - EncryptionMethod = 'Aes256' - HardwareEncryption = $true - Service = $true - SkipHardwareTest = $true - UsedSpaceOnly = $true - AllowImmediateReboot = $true - StartupKeyProtector = $true - } - - Context 'When Enable-BitlockerInternal is called and the volume is not yet encrypted' { - It 'Should enable Bitlocker with the correct key protectors and parameters' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } - Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedOSBLV } - Mock -CommandName Start-Sleep -Verifiable - Mock -CommandName Restart-Computer -Verifiable - - Enable-BitlockerInternal @defaultEnableParams - } - } - - Context 'When Enable-BitlockerInternal is called, the volume is not yet encrypted, and Enable-Bitlocker does not return a result' { - It 'Should throw an exception' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } - Mock -CommandName Enable-Bitlocker -Verifiable - - { Enable-BitlockerInternal @defaultEnableParams } | Should -Throw -ExpectedMessage 'Failed to successfully enable Bitlocker on MountPoint' - } - } - - Context 'When Enable-BitlockerInternal is called, the volume is not yet encrypted and is not an OS drive, and AutoUnlock is specified' { - It 'Should enable Bitlocker with the correct key protectors and parameters and enable AutoUnlock' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { - return @{ - VolumeStatus = 'FullyDecrypted' - VolumeType = 'Data' - } - } - Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedBLV } - Mock -CommandName Enable-BitlockerAutoUnlock -Verifiable - - $defaultEnableParams.Add('AutoUnlock', $true) - - Enable-BitlockerInternal @defaultEnableParams - - $defaultEnableParams.Remove('AutoUnlock') - } - } - - Context 'When Enable-BitlockerInternal is called with TPM only and the volume is not yet encrypted' { - It 'Should enable Bitlocker with the correct key protectors and parameters' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } - Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedBLV } - - $tpmOnlyEnableParams = @{ - MountPoint = $mountPoint - PrimaryProtector = 'TpmProtector' - TpmProtector = $true - } - - Enable-BitlockerInternal @tpmOnlyEnableParams - } - } - - Context 'When Enable-BitlockerInternal is called with TPM and pin only and the volume is not yet encrypted' { - It 'Should enable Bitlocker with the correct key protectors and parameters' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } - Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedBLV } - - $tpmAndPinOnlyEnableParams = @{ - MountPoint = $mountPoint - PrimaryProtector = 'TpmProtector' - TpmProtector = $true - Pin = $fakePin - } - - Enable-BitlockerInternal @tpmAndPinOnlyEnableParams - } - } - - Context 'When Enable-BitlockerInternal is called with TPM and pin only and the volume is not yet encrypted' { - It 'Should enable Bitlocker with the correct key protectors and parameters' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } - Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedBLV } - - $tpmAndStartupOnlyEnableParams = @{ - MountPoint = $mountPoint - PrimaryProtector = 'TpmProtector' - TpmProtector = $true - StartupKeyProtector = $true - StartupKeyPath = 'C:\' - } - - Enable-BitlockerInternal @tpmAndStartupOnlyEnableParams - } - } - - Context 'When Enable-BitlockerInternal is called with a Password Protector and the volume is not yet encrypted' { - It 'Should enable Bitlocker with the correct key protectors and parameters' { - Mock -CommandName Get-BitLockerVolume -MockWith { return $decryptedOSBLV } - Mock -CommandName Enable-Bitlocker -MockWith { return $encryptedBLV } - - $passwordEnableParams = @{ - MountPoint = $mountPoint - PrimaryProtector = 'PasswordProtector' - PasswordProtector = $true - Password = $fakePin - } - - Enable-BitlockerInternal @passwordEnableParams - } - } - - Context 'When Enable-BitlockerInternal is called with a Recovery Password Protector and the volume is not yet encrypted' { - It 'Should enable Bitlocker with the correct key protectors and parameters' { - Mock -CommandName Get-BitLockerVolume -MockWith { return $decryptedOSBLV } - Mock -CommandName Enable-Bitlocker -MockWith { return $encryptedBLV } - - $recoveryPasswordEnableParams = @{ - MountPoint = $mountPoint - PrimaryProtector = 'RecoveryPasswordProtector' - RecoveryPasswordProtector = $true - Password = $fakePin - } - - Enable-BitlockerInternal @recoveryPasswordEnableParams - } - } - - Context 'When Enable-BitlockerInternal is called with a StartupKey Protector and the volume is not yet encrypted' { - It 'Should enable Bitlocker with the correct key protectors and parameters' { - Mock -CommandName Get-BitLockerVolume -MockWith { return $decryptedOSBLV } - Mock -CommandName Enable-Bitlocker -MockWith { return $encryptedBLV } - - $startupKeyEnableParams = @{ - MountPoint = $mountPoint - PrimaryProtector = 'StartupKeyProtector' - StartupKeyProtector = $true - StartupKeyPath = 'C:\Path' - } - - Enable-BitlockerInternal @startupKeyEnableParams - } - } - } - - Describe 'xBitLockerCommon\Add-MissingBitLockerKeyProtector' -Tag 'Helper' { - # Override Bitlocker cmdlets - function Add-BitLockerKeyProtector {} - - # Suppress Write-Verbose output - Mock -CommandName Write-Verbose - - AfterEach { - Assert-VerifiableMock - } - - $fakePin = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'fakepin', (New-Object -TypeName System.Security.SecureString) - $mountPoint = 'C:' - $encryptedBLV = @{ - VolumeStatus = 'FullyEncrypted' - } - - Context 'When Add-MissingBitLockerKeyProtector is called, the AdAccountOrGroupProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { - It 'Should add the AdAccountOrGroupProtector protector' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false} - Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'AdAccountOrGroupProtector'} - - Enable-BitlockerInternal -MountPoint 'AdAccountOrGroupProtector' -Pin $fakePin -PrimaryProtector 'TpmProtector' -TpmProtector $true -AdAccountOrGroupProtector $true - } - } - - Context 'When Add-MissingBitLockerKeyProtector is called, the PasswordProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { - It 'Should add the PasswordProtector protector' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false} - Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'PasswordProtector'} - - Enable-BitlockerInternal -MountPoint 'PasswordProtector' -Pin $fakePin -PrimaryProtector 'TpmProtector' -TpmProtector $true -PasswordProtector $true - } - } - - Context 'When Add-MissingBitLockerKeyProtector is called, the RecoveryKeyProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { - It 'Should add the RecoveryKeyProtector protector' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false} - Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'RecoveryKeyProtector'} - - Enable-BitlockerInternal -MountPoint 'RecoveryKeyProtector' -Pin $fakePin -PrimaryProtector 'TpmProtector' -TpmProtector $true -RecoveryKeyProtector $true - } - } - - Context 'When Add-MissingBitLockerKeyProtector is called, the RecoveryPasswordProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { - It 'Should add the RecoveryPasswordProtector protector' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false} - Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'RecoveryPasswordProtector'} - - Enable-BitlockerInternal -MountPoint 'RecoveryPasswordProtector' -Pin $fakePin -PrimaryProtector 'TpmProtector' -TpmProtector $true -RecoveryPasswordProtector $true - } - } - - Context 'When Add-MissingBitLockerKeyProtector is called, the StartupKeyProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { - It 'Should add the StartupKeyProtector protector' { - Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } - Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false} - Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'StartupKeyProtector'} - - Enable-BitlockerInternal -MountPoint 'StartupKeyProtector' -PrimaryProtector 'RecoveryPasswordProtector' -RecoveryPasswordProtector $true -StartupKeyProtector $true - } - } - } - - Describe 'xBitLockerCommon\Test-CollectionContainsKeyProtector' -Tag 'Helper' { - $testKeyProtectorCollection = @( - @{ - KeyProtectorType = 'RecoveryPassword' - } - - @{ - KeyProtectorType = 'AdAccountOrGroup' - } - - @{ - KeyProtectorType = 'StartupKeyProtector' - } - ) - - Context 'When Test-CollectionContainsKeyProtector is called and the target KeyProtector exists in the collection' { - It 'Should return True' { - Test-CollectionContainsKeyProtector -Type 'AdAccountOrGroup' -KeyProtectorCollection $testKeyProtectorCollection | Should -Be $true - } - } - - Context 'When Test-CollectionContainsKeyProtector is called and the target KeyProtector does not exist in the collection' { - It 'Should return False' { - Test-CollectionContainsKeyProtector -Type 'AdAccountOrGroup2' -KeyProtectorCollection $testKeyProtectorCollection | Should -Be $false - } - } - - Context 'When Test-CollectionContainsKeyProtector is called with the StartsWith switch and the target KeyProtector exists in the collection' { - It 'Should return True' { - Test-CollectionContainsKeyProtector -Type 'AdAccount' -KeyProtectorCollection $testKeyProtectorCollection -StartsWith $true | Should -Be $true - } - } - - Context 'When Test-CollectionContainsKeyProtector is called with the StartsWith switch and the target KeyProtector does not exist in the collection' { - It 'Should return False' { - Test-CollectionContainsKeyProtector -Type 'Account' -KeyProtectorCollection $testKeyProtectorCollection -StartsWith $true | Should -Be $false - } - } - - Context 'When Test-CollectionContainsKeyProtector is called with the Contains switch and the target KeyProtector exists in the collection' { - It 'Should return True' { - Test-CollectionContainsKeyProtector -Type 'Account' -KeyProtectorCollection $testKeyProtectorCollection -Contains $true | Should -Be $true - } - } - - Context 'When Test-CollectionContainsKeyProtector is called with the Contains switch and the target KeyProtector does not exist in the collection' { - It 'Should return False' { - Test-CollectionContainsKeyProtector -Type 'NotInCollection' -KeyProtectorCollection $testKeyProtectorCollection -Contains $true | Should -Be $false - } - } - } - - Describe 'xBitLockerCommon\Add-ToPSBoundParametersFromHashtable' -Tag 'Helper' { - AfterEach { - Assert-VerifiableMock - } - - Context 'When Add-ToPSBoundParametersFromHashtable is called, a parameter is added, and a parameter is changed' { - It 'Should add a new parameter and change the existing parameter' { - $param1 = 'abc' - $param2 = $null - $param2new = 'notnull' - $param3 = 'def' - $param4 = 'ghi' - - $psBoundParametersIn = @{ - Param1 = $param1 - Param2 = $param2 - Param3 = $param3 - } - - $paramsToAdd = @{ - Param2 = $param2new - Param4 = $param4 - } - - Add-ToPSBoundParametersFromHashtable -PSBoundParametersIn $psBoundParametersIn -ParamsToAdd $paramsToAdd - - $psBoundParametersIn.ContainsKey('Param1') -and $psBoundParametersIn['Param1'] -eq $param1 | Should -Be $true - $psBoundParametersIn.ContainsKey('Param2') -and $psBoundParametersIn['Param2'] -eq $param2new | Should -Be $true - $psBoundParametersIn.ContainsKey('Param3') -and $psBoundParametersIn['Param3'] -eq $param3 | Should -Be $true - $psBoundParametersIn.ContainsKey('Param4') -and $psBoundParametersIn['Param4'] -eq $param4 | Should -Be $true - } - } - } - - Describe 'xBitLockerCommon\Remove-FromPSBoundParametersUsingHashtable' -Tag 'Helper' { - AfterEach { - Assert-VerifiableMock - } - - Context 'When Remove-FromPSBoundParametersUsingHashtable is called and both ParamsToKeep and ParamsToRemove are specified' { - It 'Should throw an exception' { - { Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn @{} -ParamsToKeep @('Param1') -ParamsToRemove @('Param2') } | ` - Should -Throw -ExpectedMessage 'Parameter set cannot be resolved using the specified named parameters.' - } - } - - Context 'When Remove-FromPSBoundParametersUsingHashtable is called with ParamsToKeep' { - It 'Should remove any parameter not specified in ParamsToKeep' { - $psBoundParametersIn = @{ - Param1 = 1 - Param2 = 2 - Param3 = 3 - } - - $paramsToKeep = @('Param1', 'Param2') - - Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn $psBoundParametersIn -ParamsToKeep $paramsToKeep - - $psBoundParametersIn.ContainsKey('Param1') | Should -Be $true - $psBoundParametersIn.ContainsKey('Param2') | Should -Be $true - $psBoundParametersIn.ContainsKey('Param3') | Should -Be $false - } - } - - Context 'When Remove-FromPSBoundParametersUsingHashtable is called with ParamsToRemove' { - It 'Should remove any parameter specified in ParamsToRemove' { - $psBoundParametersIn = @{ - Param1 = 1 - Param2 = 2 - Param3 = 3 - } - - $paramsToRemove = @( - 'Param1', - 'param2' - ) - - Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn $psBoundParametersIn -ParamsToRemove $paramsToRemove - - $psBoundParametersIn.ContainsKey('Param1') | Should -Be $false - $psBoundParametersIn.ContainsKey('Param2') | Should -Be $false - $psBoundParametersIn.ContainsKey('Param3') | Should -Be $true - } - } - } - } -} -finally -{ -} diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 452a96d..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,42 +0,0 @@ -#---------------------------------# -# environment configuration # -#---------------------------------# - -version: 1.1.{build}.0 -environment: - gallery_api: - secure: 9ekJzfsPCDBkyLrfmov83XbbhZ6E2N3z+B/Io8NbDetbHc6hWS19zsDmy7t0Vvxv - -install: - - git clone https://github.com/PowerShell/DscResource.Tests - - ps: Write-Verbose -Message "PowerShell version $($PSVersionTable.PSVersion)" -Verbose - - ps: Import-Module -Name "$env:APPVEYOR_BUILD_FOLDER\DscResource.Tests\AppVeyor.psm1" - - ps: Invoke-AppveyorInstallTask - -#---------------------------------# -# build configuration # -#---------------------------------# - -build: false - -#---------------------------------# -# test configuration # -#---------------------------------# - -test_script: - - ps: | - Invoke-AppveyorTestScriptTask -CodeCoverage -CodeCovIo - -# scripts to run before deployment -after_test: - - ps: | - Import-Module -Name "$env:APPVEYOR_BUILD_FOLDER\DscResource.Tests\AppVeyor.psm1" - Invoke-AppveyorAfterTestTask - -#---------------------------------# -# deployment configuration # -#---------------------------------# - -deploy_script: - - ps: | - Invoke-AppVeyorDeployTask diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..bff65d5 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,227 @@ +trigger: + branches: + include: + - master + paths: + include: + - source/* + tags: + include: + - "v*" + exclude: + - "*-*" +variables: + buildFolderName: output + buildArtifactName: output + testResultFolderName: testResults + testArtifactName: testResults + +stages: + - stage: Build + jobs: + - job: Package_Module + displayName: 'Package Module' + pool: + vmImage: 'ubuntu 16.04' + steps: + - task: GitVersion@5 + name: gitVersion + displayName: 'Evaluate Next Version' + inputs: + runtime: 'core' + configFilePath: 'GitVersion.yml' + - task: PowerShell@2 + name: package + displayName: 'Build & Package Module' + inputs: + filePath: './build.ps1' + arguments: '-ResolveDependency -tasks pack' + pwsh: true + env: + ModuleVersion: $(gitVersion.NuGetVersionV2) + - task: PublishBuildArtifacts@1 + displayName: 'Publish Build Artifact' + inputs: + pathToPublish: '$(buildFolderName)/' + artifactName: $(buildArtifactName) + publishLocation: 'Container' + + - stage: Test + dependsOn: Build + jobs: + - job: Test_HQRM + displayName: 'HQRM' + pool: + vmImage: 'windows-2019' + timeoutInMinutes: 0 + steps: + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + downloadType: 'single' + artifactName: 'output' + downloadPath: '$(Build.SourcesDirectory)' + - task: PowerShell@2 + name: test + displayName: 'Run HQRM Test' + inputs: + filePath: './build.ps1' + arguments: '-Tasks hqrmtest' + pwsh: false + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: 'output/testResults/NUnit*.xml' + testRunTitle: 'HQRM' + + - job: Test_Unit + displayName: 'Unit' + pool: + vmImage: 'windows-2019' + timeoutInMinutes: 0 + steps: + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + downloadType: 'single' + artifactName: $(buildArtifactName) + downloadPath: '$(Build.SourcesDirectory)' + - task: PowerShell@2 + name: test + displayName: 'Run Unit Test' + inputs: + filePath: './build.ps1' + arguments: "-Tasks test -PesterScript 'tests/Unit'" + pwsh: true + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Unit (Windows Server Core)' + - task: PublishBuildArtifacts@1 + displayName: 'Publish Test Artifact' + inputs: + pathToPublish: '$(buildFolderName)/$(testResultFolderName)/' + artifactName: $(testArtifactName) + publishLocation: 'Container' + + - job: Test_Integration + displayName: 'Integration' + pool: + vmImage: 'windows-2019' + timeoutInMinutes: 0 + steps: + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + downloadType: 'single' + artifactName: $(buildArtifactName) + downloadPath: '$(Build.SourcesDirectory)' + - task: PowerShell@2 + name: configureWinRM + displayName: 'Configure WinRM' + inputs: + targetType: 'inline' + script: 'winrm quickconfig -quiet' + pwsh: false + - powershell: | + ./build.ps1 -Tasks test -CodeCoverageThreshold 0 -PesterScript 'tests/Integration' + name: test + displayName: 'Run Integration Test' + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Integration (SQL Server 2016 / Windows Server 2019)' + + - job: Code_Coverage + displayName: 'Publish Code Coverage' + dependsOn: Test_Unit + pool: + vmImage: 'ubuntu 16.04' + timeoutInMinutes: 0 + steps: + - pwsh: | + $repositoryOwner,$repositoryName = $env:BUILD_REPOSITORY_NAME -split '/' + echo "##vso[task.setvariable variable=RepositoryOwner;isOutput=true]$repositoryOwner" + echo "##vso[task.setvariable variable=RepositoryName;isOutput=true]$repositoryName" + name: dscBuildVariable + displayName: 'Set Environment Variables' + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + downloadType: 'single' + artifactName: $(buildArtifactName) + downloadPath: '$(Build.SourcesDirectory)' + - task: DownloadBuildArtifacts@0 + displayName: 'Download Test Artifact' + inputs: + buildType: 'current' + downloadType: 'single' + artifactName: $(testArtifactName) + downloadPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: PublishCodeCoverageResults@1 + displayName: 'Publish Azure Code Coverage' + condition: succeededOrFailed() + inputs: + codeCoverageTool: 'JaCoCo' + summaryFileLocation: '$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml' + pathToSources: '$(Build.SourcesDirectory)/$(buildFolderName)/$(dscBuildVariable.RepositoryName)' + - script: | + bash <(curl -s https://codecov.io/bash) -f "./$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml" -F unit + displayName: 'Upload to Codecov.io' + condition: succeededOrFailed() + + - stage: Deploy + dependsOn: Test + condition: | + and( + succeeded(), + or( + eq(variables['Build.SourceBranch'], 'refs/heads/master'), + startsWith(variables['Build.SourceBranch'], 'refs/tags/') + ), + contains(variables['System.TeamFoundationCollectionUri'], 'dsccommunity') + ) + jobs: + - job: Deploy_Module + displayName: 'Deploy Module' + pool: + vmImage: 'ubuntu 16.04' + steps: + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + downloadType: 'single' + artifactName: $(buildArtifactName) + downloadPath: '$(Build.SourcesDirectory)' + - task: PowerShell@2 + name: publishRelease + displayName: 'Publish Release' + inputs: + filePath: './build.ps1' + arguments: '-tasks publish' + pwsh: true + env: + GitHubToken: $(GitHubToken) + GalleryApiToken: $(GalleryApiToken) + - task: PowerShell@2 + name: sendChangelogPR + displayName: 'Send Changelog PR' + inputs: + filePath: './build.ps1' + arguments: '-tasks Create_ChangeLog_GitHub_PR' + pwsh: true + env: + GitHubToken: $(GitHubToken) diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..4630cb8 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,381 @@ +<# + +.DESCRIPTION + Bootstrap and build script for PowerShell module pipeline + +#> +[CmdletBinding()] +param +( + [Parameter(Position = 0)] + [string[]]$Tasks = '.', + + [Parameter()] + [String] + $CodeCoverageThreshold = '', + + [Parameter()] + [validateScript( + { Test-Path -Path $_ } + )] + $BuildConfig, + + [Parameter()] + # A Specific folder to build the artefact into. + $OutputDirectory = 'output', + + [Parameter()] + # Subdirectory name to build the module (under $OutputDirectory) + $BuiltModuleSubdirectory = '', + + # Can be a path (relative to $PSScriptRoot or absolute) to tell Resolve-Dependency & PSDepend where to save the required modules, + # or use CurrentUser, AllUsers to target where to install missing dependencies + # You can override the value for PSDepend in the Build.psd1 build manifest + # This defaults to $OutputDirectory/modules (by default: ./output/modules) + [Parameter()] + $RequiredModulesDirectory = $(Join-Path 'output' 'RequiredModules'), + + [Parameter()] + [object[]] + $PesterScript, + + # Filter which tags to run when invoking Pester tests + # This is used in the Invoke-Pester.pester.build.ps1 tasks + [Parameter()] + [string[]] + $PesterTag, + + # Filter which tags to exclude when invoking Pester tests + # This is used in the Invoke-Pester.pester.build.ps1 tasks + [Parameter()] + [string[]] + $PesterExcludeTag, + + # Filter which tags to run when invoking DSC Resource tests + # This is used in the DscResource.Test.build.ps1 tasks + [Parameter()] + [string[]] + $DscTestTag, + + # Filter which tags to exclude when invoking DSC Resource tests + # This is used in the DscResource.Test.build.ps1 tasks + [Parameter()] + [string[]] + $DscTestExcludeTag, + + [Parameter()] + [Alias('bootstrap')] + [switch]$ResolveDependency, + + [Parameter(DontShow)] + [AllowNull()] + $BuildInfo, + + [Parameter()] + [switch] + $AutoRestore +) + +# The BEGIN block (at the end of this file) handles the Bootstrap of the Environment before Invoke-Build can run the tasks +# if the -ResolveDependency (aka Bootstrap) is specified, the modules are already available, and can be auto loaded + +process +{ + + if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1') + { + # Only run the process block through InvokeBuild (Look at the Begin block at the bottom of this script) + return + } + + # Execute the Build Process from the .build.ps1 path. + Push-Location -Path $PSScriptRoot -StackName BeforeBuild + + try + { + Write-Host -ForeGroundColor magenta "[build] Parsing defined tasks" + + # Load Default BuildInfo if not provided as parameter + if (!$PSBoundParameters.ContainsKey('BuildInfo')) + { + try + { + if (Test-Path $BuildConfig) + { + $ConfigFile = (Get-Item -Path $BuildConfig) + Write-Host "[build] Loading Configuration from $ConfigFile" + $BuildInfo = switch -Regex ($ConfigFile.Extension) + { + # Native Support for PSD1 + '\.psd1' + { + Import-PowerShellDataFile -Path $BuildConfig + } + # Support for yaml when module PowerShell-Yaml is available + '\.[yaml|yml]' + { + Import-Module -ErrorAction Stop -Name 'powershell-yaml' + ConvertFrom-Yaml -Yaml (Get-Content -Raw $ConfigFile) + } + # Native Support for JSON and JSONC (by Removing comments) + '\.[json|jsonc]' + { + $JSONC = (Get-Content -Raw -Path $ConfigFile) + $JSON = $JSONC -replace '(?m)\s*//.*?$' -replace '(?ms)/\*.*?\*/' + # This should probably be converted to hashtable for splatting + $JSON | ConvertFrom-Json + } + default + { + Write-Error "Extension '$_' not supported. using @{}" + @{ } + } + } + } + else + { + Write-Host -Object "Configuration file $BuildConfig not found" -ForegroundColor Red + $BuildInfo = @{ } + } + } + catch + { + Write-Host -Object "Error loading Config $ConfigFile.`r`n Are you missing dependencies?" -ForegroundColor Yellow + Write-Host -Object "Make sure you run './build.ps1 -ResolveDependency -tasks noop' to restore the Required modules the first time" -ForegroundColor Yellow + $BuildInfo = @{ } + Write-Error $_.Exception.Message + } + } + + # If the Invoke-Build Task Header is specified in the Build Info, set it + if ($BuildInfo.TaskHeader) + { + Set-BuildHeader ([scriptblock]::Create($BuildInfo.TaskHeader)) + } + + # Import Tasks from modules via their exported aliases when defined in BUild Manifest + # https://github.com/nightroman/Invoke-Build/tree/master/Tasks/Import#example-2-import-from-a-module-with-tasks + if ($BuildInfo.containsKey('ModuleBuildTasks')) + { + foreach ($Module in $BuildInfo['ModuleBuildTasks'].Keys) + { + try + { + Write-Host -ForegroundColor DarkGray -Verbose "Importing tasks from module $Module" + $LoadedModule = Import-Module $Module -PassThru -ErrorAction Stop + foreach ($TaskToExport in $BuildInfo['ModuleBuildTasks'].($Module)) + { + $LoadedModule.ExportedAliases.GetEnumerator().Where{ + # using -like to support wildcard + Write-Host -ForegroundColor DarkGray "`t Loading $($_.Key)..." + $_.Key -like $TaskToExport + }.ForEach{ + # Dot sourcing the Tasks via their exported aliases + . (Get-Alias $_.Key) + } + } + } + catch + { + Write-Host -ForegroundColor Red -Object "Could not load tasks for module $Module." + Write-Error $_ + } + } + } + + # Loading Build Tasks defined in the .build/ folder (will override the ones imported above if same task name) + Get-ChildItem -Path ".build/" -Recurse -Include *.ps1 -ErrorAction Ignore | ForEach-Object { + "Importing file $($_.BaseName)" | Write-Verbose + . $_.FullName + } + + # Synopsis: Empty task, useful to test the bootstrap process + task noop { } + + # Define default task sequence ("."), can be overridden in the $BuildInfo + task . { + Write-Build Yellow "No sequence currently defined for the default task" + } + + # Load Invoke-Build task sequences/workflows from $BuildInfo + Write-Host -ForegroundColor DarkGray "Adding Workflow from configuration:" + foreach ($Workflow in $BuildInfo.BuildWorkflow.keys) + { + Write-Verbose "Creating Build Workflow '$Workflow' with tasks $($BuildInfo.BuildWorkflow.($Workflow) -join ', ')" + $WorkflowItem = $BuildInfo.BuildWorkflow.($Workflow) + if ($WorkflowItem.Trim() -match '^\{(?[\w\W]*)\}$') + { + $WorkflowItem = [ScriptBlock]::Create($Matches['sb']) + } + Write-Host -ForegroundColor DarkGray " +-> $Workflow" + task $Workflow $WorkflowItem + } + + Write-Host -ForeGroundColor magenta "[build] Executing requested workflow: $($Tasks -join ', ')" + + } + finally + { + Pop-Location -StackName BeforeBuild + } +} + +Begin +{ + # Find build config if not specified + if (-not $BuildConfig) { + $config = Get-ChildItem -Path "$PSScriptRoot\*" -Include 'build.y*ml', 'build.psd1', 'build.json*' -ErrorAction:Ignore + if (-not $config -or ($config -is [array] -and $config.Length -le 0)) { + throw "No build configuration found. Specify path via -BuildConfig" + } + elseif ($config -is [array]) { + if ($config.Length -gt 1) { + throw "More than one build configuration found. Specify which one to use via -BuildConfig" + } + $BuildConfig = $config[0] + } + else { + $BuildConfig = $config + } + } + # Bootstrapping the environment before using Invoke-Build as task runner + + if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1') + { + Write-Host -foregroundColor Green "[pre-build] Starting Build Init" + Push-Location $PSScriptRoot -StackName BuildModule + } + + if ($RequiredModulesDirectory -in @('CurrentUser', 'AllUsers')) + { + # Installing modules instead of saving them + Write-Host -foregroundColor Green "[pre-build] Required Modules will be installed for $RequiredModulesDirectory, not saved." + # Tell Resolve-Dependency to use provided scope as the -PSDependTarget if not overridden in Build.psd1 + $PSDependTarget = $RequiredModulesDirectory + } + else + { + if (-Not (Split-Path -IsAbsolute -Path $OutputDirectory)) + { + $OutputDirectory = Join-Path -Path $PSScriptRoot -ChildPath $OutputDirectory + } + + # Resolving the absolute path to save the required modules to + if (-Not (Split-Path -IsAbsolute -Path $RequiredModulesDirectory)) + { + $RequiredModulesDirectory = Join-Path -Path $PSScriptRoot -ChildPath $RequiredModulesDirectory + } + + # Create the output/modules folder if not exists, or resolve the Absolute path otherwise + if (Resolve-Path $RequiredModulesDirectory -ErrorAction SilentlyContinue) + { + Write-Debug "[pre-build] Required Modules path already exist at $RequiredModulesDirectory" + $RequiredModulesPath = Convert-Path $RequiredModulesDirectory + } + else + { + Write-Host -foregroundColor Green "[pre-build] Creating required modules directory $RequiredModulesDirectory." + $RequiredModulesPath = (New-Item -ItemType Directory -Force -Path $RequiredModulesDirectory).FullName + } + + # Prepending $RequiredModulesPath folder to PSModulePath to resolve from this folder FIRST + if ($RequiredModulesDirectory -notIn @('CurrentUser', 'AllUsers') -and + (($Env:PSModulePath -split [io.path]::PathSeparator) -notContains $RequiredModulesDirectory)) + { + Write-Host -foregroundColor Green "[pre-build] Prepending '$RequiredModulesDirectory' folder to PSModulePath" + $Env:PSModulePath = $RequiredModulesDirectory + [io.path]::PathSeparator + $Env:PSModulePath + } + + # Checking if the user should -ResolveDependency + if ((!(Get-Module -ListAvailable powershell-yaml) -or !(Get-Module -ListAvailable InvokeBuild) -or !(Get-Module -ListAvailable PSDepend)) -and !$ResolveDependency) + { + if ($AutoRestore -or !$PSBoundParameters.ContainsKey('Tasks') -or $Tasks -contains 'build') + { + Write-Host -ForegroundColor Yellow "[pre-build] Dependency missing, running './build.ps1 -ResolveDependency -Tasks noop' for you `r`n" + $ResolveDependency = $true + } + else + { + Write-Warning "Some required Modules are missing, make sure you first run with the '-ResolveDependency' parameter." + Write-Warning "Running 'build.ps1 -ResolveDependency -Tasks noop' will pull required modules without running the build task." + } + } + + if ($BuiltModuleSubdirectory) + { + if (-Not (Split-Path -IsAbsolute $BuiltModuleSubdirectory)) + { + $BuildModuleOutput = Join-Path $OutputDirectory $BuiltModuleSubdirectory + } + else + { + $BuildModuleOutput = $BuiltModuleSubdirectory + } + } + else + { + $BuildModuleOutput = $OutputDirectory + } + + # Prepending $BuildModuleOutput folder to PSModulePath to resolve built module from this folder + if (($Env:PSModulePath -split [io.path]::PathSeparator) -notContains $BuildModuleOutput) + { + Write-Host -foregroundColor Green "[pre-build] Prepending '$BuildModuleOutput' folder to PSModulePath" + $Env:PSModulePath = $BuildModuleOutput + [io.path]::PathSeparator + $Env:PSModulePath + } + + # Tell Resolve-Dependency to use $RequiredModulesPath as -PSDependTarget if not overridden in Build.psd1 + $PSDependTarget = $RequiredModulesPath + } + + if ($ResolveDependency) + { + Write-Host -Object "[pre-build] Resolving dependencies." -foregroundColor Green + $ResolveDependencyParams = @{ } + + # If BuildConfig is a Yaml file, bootstrap powershell-yaml via ResolveDependency + if ($BuildConfig -match '\.[yaml|yml]$') + { + $ResolveDependencyParams.add('WithYaml', $True) + } + + $ResolveDependencyAvailableParams = (Get-Command -Name '.\Resolve-Dependency.ps1').parameters.keys + foreach ($CmdParameter in $ResolveDependencyAvailableParams) + { + + # The parameter has been explicitly used for calling the .build.ps1 + if ($MyInvocation.BoundParameters.ContainsKey($CmdParameter)) + { + $ParamValue = $MyInvocation.BoundParameters.ContainsKey($CmdParameter) + Write-Debug " adding $CmdParameter :: $ParamValue [from user-provided parameters to Build.ps1]" + $ResolveDependencyParams.Add($CmdParameter, $ParamValue) + } + # Use defaults parameter value from Build.ps1, if any + else + { + if ($ParamValue = Get-Variable -Name $CmdParameter -ValueOnly -ErrorAction Ignore) + { + Write-Debug " adding $CmdParameter :: $ParamValue [from default Build.ps1 variable]" + $ResolveDependencyParams.add($CmdParameter, $ParamValue) + } + } + } + + Write-Host -foregroundColor Green "[pre-build] Starting bootstrap process." + .\Resolve-Dependency.ps1 @ResolveDependencyParams + } + + if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1') + { + Write-Verbose "Bootstrap completed. Handing back to InvokeBuild." + if ($PSBoundParameters.ContainsKey('ResolveDependency')) + { + Write-Verbose "Dependency already resolved. Removing task" + $null = $PSBoundParameters.Remove('ResolveDependency') + } + Write-Host -foregroundColor Green "[build] Starting build with InvokeBuild." + Invoke-Build @PSBoundParameters -Task $Tasks -File $MyInvocation.MyCommand.Path + Pop-Location -StackName BuildModule + return + } +} diff --git a/build.yaml b/build.yaml new file mode 100644 index 0000000..32bddb4 --- /dev/null +++ b/build.yaml @@ -0,0 +1,97 @@ +--- +#################################################### +# Pipeline Build Task Configuration (Invoke-Build) # +#################################################### +BuildWorkflow: + '.': + - build + - test + + build: + - Clean + - Build_Module_ModuleBuilder + - Build_NestedModules_ModuleBuilder + - Create_Changelog_Release_Output + + pack: + - build + - package_module_nupkg + + hqrmtest: + - DscResource_Tests_Stop_On_Fail + + test: + - Pester_Tests_Stop_On_Fail + - Pester_If_Code_Coverage_Under_Threshold + + publish: + - Publish_Release_To_GitHub + - Publish_Module_To_gallery + +#################################################### +# ModuleBuilder Configuration # +#################################################### +CopyPaths: + - DSCResources + - en-US + - Modules +Encoding: UTF8 +VersionedOutputDirectory: true + +ModuleBuildTasks: + Sampler: + - '*.build.Sampler.ib.tasks' + +TaskHeader: | + param($Path) + "" + "=" * 79 + Write-Build Cyan "`t`t`t$($Task.Name.replace("_"," ").ToUpper())" + Write-Build DarkGray "$(Get-BuildSynopsis $Task)" + "-" * 79 + Write-Build DarkGray " $Path" + Write-Build DarkGray " $($Task.InvocationInfo.ScriptName):$($Task.InvocationInfo.ScriptLineNumber)" + "" + +#################################################### +# Pester Configuration (Sampler) # +#################################################### +Pester: + OutputFormat: NUnitXML + ExcludeFromCodeCoverage: + Script: + - tests/Unit + ExcludeTag: + Tag: + CodeCoverageThreshold: 85 + CodeCoverageOutputFile: JaCoCo_coverage.xml + CodeCoverageOutputFileEncoding: ascii + +#################################################### +# Pester Configuration (DscResource.Test) # +#################################################### +DscTest: + ExcludeTag: + - "Common Tests - New Error-Level Script Analyzer Rules" + Tag: + ExcludeSourceFile: + - output + ExcludeModuleFile: + +#################################################### +# PSDepend Configuration # +#################################################### +Resolve-Dependency: + Gallery: 'PSGallery' + AllowPrerelease: false + Verbose: false + +#################################################### +# GitHub Configuration # +#################################################### +GitHubConfig: + GitHubFilesToAdd: + - 'CHANGELOG.md' + GitHubConfigUserName: dscbot + GitHubConfigUserEmail: dsccommunity@outlook.com + UpdateChangelogOnPrerelease: false diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..0e0c081 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,44 @@ +codecov: + require_ci_to_pass: no + # master should be the baseline for reporting + branch: master + +comment: + layout: "reach, diff, flags, files" + behavior: default + +coverage: + range: 50..80 + round: down + precision: 0 + + status: + project: + default: + # Set the overall project code coverage requirement to 70% + target: 70 + flags: + - unit + patch: + default: + # Set the pull request requirement to not regress overall coverage by more than 5% + # and let codecov.io set the goal for the code changed in the patch. + target: auto + threshold: 5 + flags: + - unit +flags: + unit: + paths: + - source/ + carryforward: true + +fixes: + - '\d+\.\d+\.\d+\/::source/' # move path "X.Y.Z/" => "source/" + +# Deprecated resources +ignore: + - 'source/DSCResources/MSFT_SqlServerNetwork' + - 'source/DSCResources/MSFT_SqlDatabaseOwner' + - 'source/DSCResources/MSFT_SqlDatabaseRecoveryModel' + - 'source/DSCResources/MSFT_SqlServerEndpointState' diff --git a/DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.psm1 b/source/DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.psm1 similarity index 96% rename from DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.psm1 rename to source/DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.psm1 index b103577..1fd2afb 100644 --- a/DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.psm1 +++ b/source/DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.psm1 @@ -171,7 +171,7 @@ function Get-TargetResource ) # Load helper module - Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Misc\xBitlockerCommon.psm1" -Verbose:0 + Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Modules\xBitlocker.Common" -Verbose:0 Assert-HasPrereqsForBitlocker @@ -354,7 +354,7 @@ function Set-TargetResource ) # Load helper module - Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Misc\xBitlockerCommon.psm1" -Verbose:0 + Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Modules\xBitlocker.Common" -Verbose:0 Assert-HasPrereqsForBitlocker @@ -560,7 +560,7 @@ function Test-TargetResource ) # Load helper module - Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Misc\xBitlockerCommon.psm1" -Verbose:0 + Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Modules\xBitlocker.Common" -Verbose:0 Assert-HasPrereqsForBitlocker @@ -772,12 +772,12 @@ function Get-AutoBitlockerStatus ) # First get all Bitlocker Volumes of type Data - $allBlvs = Get-BitLockerVolume | Where-Object -FilterScript {$_.VolumeType -eq 'Data'} + $allBlvs = Get-BitLockerVolume | Where-Object -FilterScript { $_.VolumeType -eq 'Data' } # Filter on size if it was specified if ($PSBoundParameters.ContainsKey('MinDiskCapacityGB')) { - $allBlvs = $allBlvs | Where-Object -FilterScript {$_.CapacityGB -ge $MinDiskCapacityGB} + $allBlvs = $allBlvs | Where-Object -FilterScript { $_.CapacityGB -ge $MinDiskCapacityGB } } # Now find disks of the appropriate drive type, and add them to the collection @@ -807,19 +807,19 @@ function Get-AutoBitlockerStatus if (Split-Path -Path $blv.MountPoint -IsAbsolute) { # MountPoint is a Drive Letter - $vol = $encryptableVolumes | Where-Object {($_.DriveLetter -eq $blv.Mountpoint) -and ($_.VolumeType -eq $driveTypeValue)} + $vol = $encryptableVolumes | Where-Object { ($_.DriveLetter -eq $blv.Mountpoint) -and ($_.VolumeType -eq $driveTypeValue) } } else { # MountPoint is a path - $vol = $encryptableVolumes | Where-Object {($_.DeviceID -eq $blv.Mountpoint) -and ($_.VolumeType -eq $driveTypeValue)} + $vol = $encryptableVolumes | Where-Object { ($_.DeviceID -eq $blv.Mountpoint) -and ($_.VolumeType -eq $driveTypeValue) } } if ($null -ne $vol) { [Hashtable] $props = @{ - VolumeStatus = $blv.VolumeStatus - KeyProtectors = $blv.KeyProtector + VolumeStatus = $blv.VolumeStatus + KeyProtectors = $blv.KeyProtector EncryptionMethod = $blv.EncryptionMethod } diff --git a/DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.schema.mof b/source/DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.schema.mof similarity index 100% rename from DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.schema.mof rename to source/DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.schema.mof diff --git a/source/DSCResources/MSFT_xBLAutoBitlocker/en-US/MSFT_xBLAutoBitlocker.strings.psd1 b/source/DSCResources/MSFT_xBLAutoBitlocker/en-US/MSFT_xBLAutoBitlocker.strings.psd1 new file mode 100644 index 0000000..3b35d66 --- /dev/null +++ b/source/DSCResources/MSFT_xBLAutoBitlocker/en-US/MSFT_xBLAutoBitlocker.strings.psd1 @@ -0,0 +1,2 @@ +ConvertFrom-StringData @' +'@ diff --git a/DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.psm1 b/source/DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.psm1 similarity index 98% rename from DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.psm1 rename to source/DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.psm1 index e83c867..f2a5cd6 100644 --- a/DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.psm1 +++ b/source/DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.psm1 @@ -166,7 +166,7 @@ function Get-TargetResource ) # Load helper module - Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Misc\xBitlockerCommon.psm1" -Verbose:0 + Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Modules\xBitlocker.Common" -Verbose:0 Assert-HasPrereqsForBitlocker @@ -344,7 +344,7 @@ function Set-TargetResource ) # Load helper module - Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Misc\xBitlockerCommon.psm1" -Verbose:0 + Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Modules\xBitlocker.Common" -Verbose:0 Assert-HasPrereqsForBitlocker @@ -520,7 +520,7 @@ function Test-TargetResource ) # Load helper module - Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Misc\xBitlockerCommon.psm1" -Verbose:0 + Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Modules\xBitlocker.Common" -Verbose:0 Assert-HasPrereqsForBitlocker diff --git a/DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.schema.mof b/source/DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.schema.mof similarity index 100% rename from DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.schema.mof rename to source/DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.schema.mof diff --git a/source/DSCResources/MSFT_xBLBitlocker/en-US/MSFT_xBLBitlocker.strings.psd1 b/source/DSCResources/MSFT_xBLBitlocker/en-US/MSFT_xBLBitlocker.strings.psd1 new file mode 100644 index 0000000..3b35d66 --- /dev/null +++ b/source/DSCResources/MSFT_xBLBitlocker/en-US/MSFT_xBLBitlocker.strings.psd1 @@ -0,0 +1,2 @@ +ConvertFrom-StringData @' +'@ diff --git a/DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.psm1 b/source/DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.psm1 similarity index 89% rename from DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.psm1 rename to source/DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.psm1 index 7633a7d..c810779 100644 --- a/DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.psm1 +++ b/source/DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.psm1 @@ -20,7 +20,7 @@ #> function Get-TargetResource { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSDSCUseVerboseMessageInDSCResource', '')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSDSCUseVerboseMessageInDSCResource', '')] [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param @@ -43,7 +43,7 @@ function Get-TargetResource ) # Load helper module - Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Misc\xBitlockerCommon.psm1" -Verbose:0 + Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Modules\xBitlocker.Common" -Verbose:0 Assert-HasPrereqsForBitlocker @@ -82,13 +82,13 @@ function Get-TargetResource function Set-TargetResource { # Suppressing this rule because $global:DSCMachineStatus is used to trigger a reboot. - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Scope='Function', Target='DSCMachineStatus')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')] <# Suppressing this rule because $global:DSCMachineStatus is only set, never used (by design of Desired State Configuration). #> - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Scope='Function', Target='DSCMachineStatus')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSDSCUseVerboseMessageInDSCResource', '')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSDSCUseVerboseMessageInDSCResource', '')] [CmdletBinding()] param ( @@ -110,7 +110,7 @@ function Set-TargetResource ) # Load helper module - Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Misc\xBitlockerCommon.psm1" -Verbose:0 + Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Modules\xBitlocker.Common" -Verbose:0 Assert-HasPrereqsForBitlocker @@ -163,7 +163,7 @@ function Set-TargetResource #> function Test-TargetResource { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSDSCUseVerboseMessageInDSCResource', '')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSDSCUseVerboseMessageInDSCResource', '')] [CmdletBinding()] [OutputType([System.Boolean])] param @@ -186,7 +186,7 @@ function Test-TargetResource ) # Load helper module - Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Misc\xBitlockerCommon.psm1" -Verbose:0 + Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Modules\xBitlocker.Common" -Verbose:0 Assert-HasPrereqsForBitlocker diff --git a/DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.schema.mof b/source/DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.schema.mof similarity index 100% rename from DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.schema.mof rename to source/DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.schema.mof diff --git a/source/DSCResources/MSFT_xBLTpm/en-US/MSFT_xBLTpm.strings.psd1 b/source/DSCResources/MSFT_xBLTpm/en-US/MSFT_xBLTpm.strings.psd1 new file mode 100644 index 0000000..3b35d66 --- /dev/null +++ b/source/DSCResources/MSFT_xBLTpm/en-US/MSFT_xBLTpm.strings.psd1 @@ -0,0 +1,2 @@ +ConvertFrom-StringData @' +'@ diff --git a/Examples/ConfigureBitlockerAndAutoBitlocker/ConfigureBitlockerAndAutoBitlocker.ps1 b/source/Examples/ConfigureBitlockerAndAutoBitlocker/ConfigureBitlockerAndAutoBitlocker.ps1 similarity index 98% rename from Examples/ConfigureBitlockerAndAutoBitlocker/ConfigureBitlockerAndAutoBitlocker.ps1 rename to source/Examples/ConfigureBitlockerAndAutoBitlocker/ConfigureBitlockerAndAutoBitlocker.ps1 index 78e0360..414c84c 100644 --- a/Examples/ConfigureBitlockerAndAutoBitlocker/ConfigureBitlockerAndAutoBitlocker.ps1 +++ b/source/Examples/ConfigureBitlockerAndAutoBitlocker/ConfigureBitlockerAndAutoBitlocker.ps1 @@ -46,5 +46,3 @@ Configuration ConfigureBitlockerAndAutoBitlocker } } } - -ConfigureBitlockerAndAutoBitlocker diff --git a/Examples/ConfigureBitlockerOnOSDrive/ConfigureBitlockerOnOSDrive.ps1 b/source/Examples/ConfigureBitlockerOnOSDrive/ConfigureBitlockerOnOSDrive.ps1 similarity index 97% rename from Examples/ConfigureBitlockerOnOSDrive/ConfigureBitlockerOnOSDrive.ps1 rename to source/Examples/ConfigureBitlockerOnOSDrive/ConfigureBitlockerOnOSDrive.ps1 index 0f84d06..6640463 100644 --- a/Examples/ConfigureBitlockerOnOSDrive/ConfigureBitlockerOnOSDrive.ps1 +++ b/source/Examples/ConfigureBitlockerOnOSDrive/ConfigureBitlockerOnOSDrive.ps1 @@ -35,5 +35,3 @@ Configuration ConfigureBitlockerOnOSDrive } } } - -ConfigureBitlockerOnOSDrive diff --git a/source/Modules/xBitlocker.Common/en-US/xBitlocker.Common.strings.psd1 b/source/Modules/xBitlocker.Common/en-US/xBitlocker.Common.strings.psd1 new file mode 100644 index 0000000..3b35d66 --- /dev/null +++ b/source/Modules/xBitlocker.Common/en-US/xBitlocker.Common.strings.psd1 @@ -0,0 +1,2 @@ +ConvertFrom-StringData @' +'@ diff --git a/source/Modules/xBitlocker.Common/xBitlocker.Common.psd1 b/source/Modules/xBitlocker.Common/xBitlocker.Common.psd1 new file mode 100644 index 0000000..25db007 --- /dev/null +++ b/source/Modules/xBitlocker.Common/xBitlocker.Common.psd1 @@ -0,0 +1,53 @@ +@{ + # Script module or binary module file associated with this manifest. + RootModule = 'xBitlocker.Common.psm1' + + # Version number of this module. + ModuleVersion = '0.0.1' + + # ID used to uniquely identify this module + GUID = '94239bd5-f41f-456b-b7ab-37aaaad6da86' + + # Author of this module + Author = 'DSC Community' + + # Company or vendor of this module + CompanyName = 'DSC Community' + + # Copyright statement for this module + Copyright = 'Copyright the DSC Community contributors. All rights reserved.' + + # Description of the functionality provided by this module + Description = 'Functions used by the DSC resources in xBitlocker.' + + # 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. + FunctionsToExport = @( + 'Add-MissingBitLockerKeyProtector' + 'Add-ToPSBoundParametersFromHashtable' + 'Assert-HasPrereqsForBitlocker' + 'Enable-BitlockerInternal' + 'Get-OSEdition' + 'Remove-FromPSBoundParametersUsingHashtable' + 'Test-BitlockerEnabled' + 'Test-CollectionContainsKeyProtector' + ) + + # 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. + CmdletsToExport = @() + + # Variables to export from this module + VariablesToExport = @() + + # 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. + AliasesToExport = @() + + # DSC resources to export from this module + DscResourcesToExport = @() + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + PSData = @{ + } # End of PSData hashtable + } # End of PrivateData hashtable +} + diff --git a/Misc/xBitlockerCommon.psm1 b/source/Modules/xBitlocker.Common/xBitlocker.Common.psm1 similarity index 98% rename from Misc/xBitlockerCommon.psm1 rename to source/Modules/xBitlocker.Common/xBitlocker.Common.psm1 index 814b156..fecc8c1 100644 --- a/Misc/xBitlockerCommon.psm1 +++ b/source/Modules/xBitlocker.Common/xBitlocker.Common.psm1 @@ -81,12 +81,12 @@ function Enable-BitlockerInternal { # Suppressing this rule because $global:DSCMachineStatus is used to trigger a reboot. - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Scope='Function', Target='DSCMachineStatus')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')] <# Suppressing this rule because $global:DSCMachineStatus is only set, never used (by design of Desired State Configuration). #> - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Scope='Function', Target='DSCMachineStatus')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [CmdletBinding()] param ( @@ -758,7 +758,7 @@ function Test-BitlockerEnabled } else # TpmProtector is primary { - if(!(Test-CollectionContainsKeyProtector -Type 'Tpm' -KeyProtectorCollection $blv.KeyProtector -StartsWith $true) -and !(Test-CollectionContainsKeyProtector -Type 'StartupKey' -KeyProtectorCollection $blv.KeyProtector -Contains $true)) + if (!(Test-CollectionContainsKeyProtector -Type 'Tpm' -KeyProtectorCollection $blv.KeyProtector -StartsWith $true) -and !(Test-CollectionContainsKeyProtector -Type 'StartupKey' -KeyProtectorCollection $blv.KeyProtector -Contains $true)) { Write-Verbose "MountPoint '$($MountPoint) 'does not have TPM + StartupKey protector." return $false @@ -784,7 +784,7 @@ function Test-BitlockerEnabled function Assert-HasPrereqsForBitlocker { [CmdletBinding()] - param() + param () $hasAllPreReqs = $true @@ -988,7 +988,7 @@ function Get-OSEdition { [CmdletBinding()] [OutputType([System.String])] - param() + param () return (Get-ItemProperty -Path 'HKLM:/software/microsoft/windows nt/currentversion' -Name InstallationType).InstallationType } diff --git a/source/build.psd1 b/source/build.psd1 new file mode 100644 index 0000000..03f5d6c --- /dev/null +++ b/source/build.psd1 @@ -0,0 +1,6 @@ +@{ + Path = 'xBitlocker.psd1' #or build breaks on Linux +} +# Waiting for ModuleBuilder to do away with this file +# when all parameters are provided to the function + diff --git a/source/en-US/about_xBitlocker.help.txt b/source/en-US/about_xBitlocker.help.txt new file mode 100644 index 0000000..ce8691c --- /dev/null +++ b/source/en-US/about_xBitlocker.help.txt @@ -0,0 +1,26 @@ +TOPIC + about_xBitlocker + +SHORT DESCRIPTION + This DSC module allows you to configure Bitlocker. + +LONG DESCRIPTION + This DSC module allows you to configure Bitlocker on a single disk, configure + a TPM chip, or automatically enable Bitlocker on multiple disks. + +EXAMPLES + PS C:\> Get-DscResource -Module xBitlocker + +NOTE: + Thank you to the DSC Community contributors who contributed to this module by + writing code, sharing opinions, and provided feedback. + +TROUBLESHOOTING NOTE: + Go to the Github repository for read about issues, submit a new issue, and read + about new releases. https://github.com/dsccommunity/xBitlocker + +SEE ALSO + - https://github.com/dsccommunity/xBitlocker + +KEYWORDS + DSC, DscResource, Bitlocker, TPM diff --git a/source/xBitlocker.psd1 b/source/xBitlocker.psd1 new file mode 100644 index 0000000..12eb437 --- /dev/null +++ b/source/xBitlocker.psd1 @@ -0,0 +1,69 @@ +@{ + # Version number of this module. + ModuleVersion = '0.0.1' + + # ID used to uniquely identify this module + GUID = 'dc4f3fd0-4e1d-4916-84f8-d0bb89d52507' + + # Author of this module + Author = 'DSC Community' + + # Company or vendor of this module + CompanyName = 'DSC Community' + + # Copyright statement for this module + Copyright = 'Copyright the DSC Community contributors. All rights reserved.' + + # Description of the functionality provided by this module + Description = 'This DSC Module allows you to configure Bitlocker on a single disk, configure a TPM chip, or automatically enable Bitlocker on multiple disks.' + + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '4.0' + + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + ClrVersion = '4.0' + + + # 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. + FunctionsToExport = @() + + # 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. + CmdletsToExport = @() + + # Variables to export from this module + VariablesToExport = @() + + # 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. + AliasesToExport = @() + + # DSC resources to export from this module + DscResourcesToExport = @( + 'xBLAutoBitlocker' + 'xBLBitlocker' + 'xBLTpm' + ) + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + PSData = @{ + # Tags applied to this module. These help with module discovery in online galleries. + Tags = 'DesiredStateConfiguration', 'DSC', 'DSCResourceKit', 'DSCResource' + + # A URL to the license for this module. + LicenseUri = 'https://github.com/dsccommunity/xBitlocker/blob/master/LICENSE' + + # A URL to the main website for this project. + ProjectUri = 'https://github.com/dsccommunity/xBitlocker' + + # A URL to an icon representing this module. + IconUri = 'https://dsccommunity.org/images/DSC_Logo_300p.png' + + # ReleaseNotes of this module + ReleaseNotes = '' + + # Prerelease string of this module + Prerelease = '' + } # End of PSData hashtable + } # End of PrivateData hashtable +} + diff --git a/Tests/Integration/MSFT_xBLAutoBitlocker.Integration.tests.ps1 b/tests/Integration/MSFT_xBLAutoBitlocker.Integration.tests.ps1 similarity index 71% rename from Tests/Integration/MSFT_xBLAutoBitlocker.Integration.tests.ps1 rename to tests/Integration/MSFT_xBLAutoBitlocker.Integration.tests.ps1 index 12cdc65..24a757c 100644 --- a/Tests/Integration/MSFT_xBLAutoBitlocker.Integration.tests.ps1 +++ b/tests/Integration/MSFT_xBLAutoBitlocker.Integration.tests.ps1 @@ -1,25 +1,24 @@ $script:dscModuleName = 'xBitlocker' $script:dscResourceFriendlyName = 'xBLAutoBitlocker' -$script:dcsResourceName = "MSFT_$($script:dscResourceFriendlyName)" +$script:dscResourceName = "MSFT_$($script:dscResourceFriendlyName)" -#region HEADER -# Integration Test Template Version: 1.3.1 -[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) -if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +try +{ + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' +} +catch [System.IO.FileNotFoundException] { - & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' } -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force -$TestEnvironment = Initialize-TestEnvironment ` +$script:testEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` - -DSCResourceName $script:dcsResourceName ` - -TestType Integration + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Integration' # Import xBitlocker Test Helper Module -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'Tests' -ChildPath (Join-Path -Path 'TestHelpers' -ChildPath 'xBitlockerTestHelper.psm1'))) -Force -#endregion +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath (Join-Path -Path '..' -ChildPath (Join-Path -Path 'TestHelpers' -ChildPath 'xBitlockerTestHelper.psm1'))) -Force # Make sure required features are installed before running tests if (!(Test-RequiredFeaturesInstalled)) @@ -49,11 +48,11 @@ foreach ($fixedDriveBlv in $fixedDriveBlvs) try { #region Integration Tests - $configurationFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dcsResourceName).config.ps1" + $configurationFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" . $configurationFile - Describe "$($script:dcsResourceName)_Integration" { - $configurationName = "$($script:dcsResourceName)_EnablePasswordProtectorOnDataDrives_Config" + Describe "$($script:dscResourceName)_Integration" { + $configurationName = "$($script:dscResourceName)_EnablePasswordProtectorOnDataDrives_Config" Context ('When using configuration {0}' -f $configurationName) { It 'Should compile and apply the MOF without throwing' { diff --git a/Tests/Integration/MSFT_xBLAutoBitlocker.config.ps1 b/tests/Integration/MSFT_xBLAutoBitlocker.config.ps1 similarity index 100% rename from Tests/Integration/MSFT_xBLAutoBitlocker.config.ps1 rename to tests/Integration/MSFT_xBLAutoBitlocker.config.ps1 diff --git a/Tests/Integration/MSFT_xBLBitlocker.Integration.tests.ps1 b/tests/Integration/MSFT_xBLBitlocker.Integration.tests.ps1 similarity index 67% rename from Tests/Integration/MSFT_xBLBitlocker.Integration.tests.ps1 rename to tests/Integration/MSFT_xBLBitlocker.Integration.tests.ps1 index e8e4fd0..bf28491 100644 --- a/Tests/Integration/MSFT_xBLBitlocker.Integration.tests.ps1 +++ b/tests/Integration/MSFT_xBLBitlocker.Integration.tests.ps1 @@ -1,25 +1,24 @@ $script:dscModuleName = 'xBitlocker' $script:dscResourceFriendlyName = 'xBLBitlocker' -$script:dcsResourceName = "MSFT_$($script:dscResourceFriendlyName)" +$script:dscResourceName = "MSFT_$($script:dscResourceFriendlyName)" -#region HEADER -# Integration Test Template Version: 1.3.1 -[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) -if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +try +{ + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' +} +catch [System.IO.FileNotFoundException] { - & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' } -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force -$TestEnvironment = Initialize-TestEnvironment ` +$script:testEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` - -DSCResourceName $script:dcsResourceName ` - -TestType Integration + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Integration' # Import xBitlocker Test Helper Module -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'Tests' -ChildPath (Join-Path -Path 'TestHelpers' -ChildPath 'xBitlockerTestHelper.psm1'))) -Force -#endregion +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath (Join-Path -Path '..' -ChildPath (Join-Path -Path 'TestHelpers' -ChildPath 'xBitlockerTestHelper.psm1'))) -Force # Make sure the TPM is present before running tests if (!(Test-HasPresentTpm)) @@ -37,13 +36,13 @@ if (!(Test-RequiredFeaturesInstalled)) try { #region Integration Tests - $configurationFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dcsResourceName).config.ps1" + $configurationFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" . $configurationFile - Describe "$($script:dcsResourceName)_Integration" { + Describe "$($script:dscResourceName)_Integration" { $configurationNames = @( - "$($script:dcsResourceName)_BasicTPMEncryptionOnSysDrive_Config" - "$($script:dcsResourceName)_TPMEncryptionOnSysDriveWithFalseSwitchParams_Config" + "$($script:dscResourceName)_BasicTPMEncryptionOnSysDrive_Config" + "$($script:dscResourceName)_TPMEncryptionOnSysDriveWithFalseSwitchParams_Config" ) foreach ($configurationName in $configurationNames) diff --git a/Tests/Integration/MSFT_xBLBitlocker.config.ps1 b/tests/Integration/MSFT_xBLBitlocker.config.ps1 similarity index 100% rename from Tests/Integration/MSFT_xBLBitlocker.config.ps1 rename to tests/Integration/MSFT_xBLBitlocker.config.ps1 diff --git a/Tests/Integration/MSFT_xBLTpm.Integration.tests.ps1 b/tests/Integration/MSFT_xBLTpm.Integration.tests.ps1 similarity index 64% rename from Tests/Integration/MSFT_xBLTpm.Integration.tests.ps1 rename to tests/Integration/MSFT_xBLTpm.Integration.tests.ps1 index 8890e21..a9e1d49 100644 --- a/Tests/Integration/MSFT_xBLTpm.Integration.tests.ps1 +++ b/tests/Integration/MSFT_xBLTpm.Integration.tests.ps1 @@ -1,25 +1,24 @@ $script:dscModuleName = 'xBitlocker' $script:dscResourceFriendlyName = 'xBLTpm' -$script:dcsResourceName = "MSFT_$($script:dscResourceFriendlyName)" +$script:dscResourceName = "MSFT_$($script:dscResourceFriendlyName)" -#region HEADER -# Integration Test Template Version: 1.3.1 -[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) -if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +try +{ + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' +} +catch [System.IO.FileNotFoundException] { - & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' } -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force -$TestEnvironment = Initialize-TestEnvironment ` +$script:testEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` - -DSCResourceName $script:dcsResourceName ` - -TestType Integration + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Integration' # Import xBitlocker Test Helper Module -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'Tests' -ChildPath (Join-Path -Path 'TestHelpers' -ChildPath 'xBitlockerTestHelper.psm1'))) -Force -#endregion +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath (Join-Path -Path '..' -ChildPath (Join-Path -Path 'TestHelpers' -ChildPath 'xBitlockerTestHelper.psm1'))) -Force # Make sure the TPM is present before running tests if (!(Test-HasPresentTpm)) @@ -37,11 +36,11 @@ if (!(Test-RequiredFeaturesInstalled)) try { #region Integration Tests - $configurationFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dcsResourceName).config.ps1" + $configurationFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" . $configurationFile - Describe "$($script:dcsResourceName)_Integration" { - $configurationName = "$($script:dcsResourceName)_BasicTPMInitialization_Config" + Describe "$($script:dscResourceName)_Integration" { + $configurationName = "$($script:dscResourceName)_BasicTPMInitialization_Config" Context ('When using configuration {0}' -f $configurationName) { It 'Should compile and apply the MOF without throwing' { diff --git a/Tests/Integration/MSFT_xBLTpm.config.ps1 b/tests/Integration/MSFT_xBLTpm.config.ps1 similarity index 100% rename from Tests/Integration/MSFT_xBLTpm.config.ps1 rename to tests/Integration/MSFT_xBLTpm.config.ps1 diff --git a/Tests/TestHelpers/xBitlockerTestHelper.psm1 b/tests/TestHelpers/xBitlockerTestHelper.psm1 similarity index 100% rename from Tests/TestHelpers/xBitlockerTestHelper.psm1 rename to tests/TestHelpers/xBitlockerTestHelper.psm1 diff --git a/Tests/Unit/MSFT_xBLAutoBitlocker.tests.ps1 b/tests/Unit/MSFT_xBLAutoBitlocker.tests.ps1 similarity index 85% rename from Tests/Unit/MSFT_xBLAutoBitlocker.tests.ps1 rename to tests/Unit/MSFT_xBLAutoBitlocker.tests.ps1 index 3750886..5a9a5ee 100644 --- a/Tests/Unit/MSFT_xBLAutoBitlocker.tests.ps1 +++ b/tests/Unit/MSFT_xBLAutoBitlocker.tests.ps1 @@ -1,41 +1,38 @@ #region HEADER -$script:DSCModuleName = 'xBitlocker' -$script:DSCResourceName = 'MSFT_xBLAutoBitlocker' - -# Unit Test Template Version: 1.2.4 -$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) -if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) -{ - & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) -} - -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force - -$TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -ResourceType 'Mof' ` - -TestType Unit - -#endregion HEADER +$script:dscModuleName = 'xBitlocker' +$script:dscResourceName = 'MSFT_xBLAutoBitlocker' function Invoke-TestSetup { + try + { + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' + } + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Unit' } function Invoke-TestCleanup { - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Restore-TestEnvironment -TestEnvironment $script:testEnvironment } -# Begin Testing +Invoke-TestSetup + try { - InModuleScope 'MSFT_xBLAutoBitlocker' { + InModuleScope $script:dscResourceName { # Override Bitlocker functions - function Get-BitLockerVolume { + function Get-BitLockerVolume + { param ( [Parameter()] @@ -180,7 +177,6 @@ try # Get-BitlockerVolume is used to obtain list of volumes in the system and their current encryption status Mock ` -CommandName Get-BitlockerVolume ` - -ModuleName 'MSFT_xBLAutoBitlocker' ` -MockWith { # Returns a collection of OS/Fixed/Removable disks with correct/incorrect removable status return @( @@ -281,7 +277,6 @@ try # This was used in broken version of the module, replaced in Issue #11 by Win32_EncryptableVolume class Mock ` -CommandName Get-Volume ` - -ModuleName 'MSFT_xBLAutoBitlocker' ` -MockWith { # Returns a collection of OS/Fixed/Removable disks with correct/incorrect removable status @@ -323,7 +318,6 @@ try Mock ` -CommandName Get-CimInstance ` - -ModuleName 'MSFT_xBLAutoBitlocker' ` -MockWith { # Returns a collection of OS/Fixed/Removable disks with correct/incorrect removable status return @( @@ -379,121 +373,121 @@ try Context 'When Volume C: Reports as OS Volume' { It 'Should Not Be In The List of Eligible Fixed Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector TpmProtector|Select-Object -ExpandProperty Keys)|Should -Not -Contain 'C:' + (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector TpmProtector).Keys | Should -Not -Contain 'C:' } It 'Should Not Be In The List of Eligible Removable Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector TpmProtector|Select-Object -ExpandProperty Keys)|Should -Not -Contain 'C:' + (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector TpmProtector).Keys | Should -Not -Contain 'C:' } } Context 'When Volume D: Reports Fixed to OS, but Removable to Bitlocker' { It 'Should Not Be In The List of Eligible Fixed Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Not -Contain 'D:' + (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Not -Contain 'D:' } It 'Should Be In The List of Eligible Removable Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Contain 'D:' + (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Contain 'D:' } } Context 'When Volume E: Reports Fixed to OS and Bitlocker' { It 'Should Be In The List of Eligible Fixed Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Contain 'E:' + (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Contain 'E:' } It 'Should Not Be In The List of Eligible Removable Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Not -Contain 'E:' + (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Not -Contain 'E:' } } Context 'When Volume F: Reports as Removable to OS and Bitlocker' { It 'Should Not Be In The List of Eligible Fixed Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Not -Contain 'F:' + (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Not -Contain 'F:' } It 'Should Be In The List of Eligible Removable Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Contain 'F:' + (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Contain 'F:' } } Context 'When Volume \\?\Volume{00000000-0000-0000-0000-000000000001}\ Reports Fixed to OS, but Removable to Bitlocker' { It 'Should Not Be In The List of Eligible Fixed Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Not -Contain '\\?\Volume{00000000-0000-0000-0000-000000000001}\' + (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Not -Contain '\\?\Volume{00000000-0000-0000-0000-000000000001}\' } It 'Should Be In The List of Eligible Removable Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Contain '\\?\Volume{00000000-0000-0000-0000-000000000001}\' + (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Contain '\\?\Volume{00000000-0000-0000-0000-000000000001}\' } } Context 'When Volume \\?\Volume{00000000-0000-0000-0000-000000000002}\ Reports Fixed to OS and Bitlocker' { It 'Should Be In The List of Eligible Fixed Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Contain '\\?\Volume{00000000-0000-0000-0000-000000000002}\' + (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Contain '\\?\Volume{00000000-0000-0000-0000-000000000002}\' } It 'Should Not Be In The List of Eligible Removable Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Not -Contain '\\?\Volume{00000000-0000-0000-0000-000000000002}\' + (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Not -Contain '\\?\Volume{00000000-0000-0000-0000-000000000002}\' } } Context 'When Volume \\?\Volume{00000000-0000-0000-0000-000000000003}\ Reports as Removable to OS and Bitlocker' { It 'Should Not Be In The List of Eligible Fixed Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Not -Contain '\\?\Volume{00000000-0000-0000-0000-000000000003}\' + (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Not -Contain '\\?\Volume{00000000-0000-0000-0000-000000000003}\' } It 'Should Be In The List of Eligible Removable Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Contain '\\?\Volume{00000000-0000-0000-0000-000000000003}\' + (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Contain '\\?\Volume{00000000-0000-0000-0000-000000000003}\' } } Context 'When MinDiskCapacity Parameter is Defined at 100 GB for Fixed Disks' { It 'Should Exclude E: from The List of Eligible Fixed Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector -MinDiskCapacityGB 100|Select-Object -ExpandProperty Keys)|Should -Not -Contain 'E:' + (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector -MinDiskCapacityGB 100).Keys | Should -Not -Contain 'E:' } It 'Should Include Volume \\?\Volume{00000000-0000-0000-0000-000000000002}\ In The List of Eligible Removable Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector -MinDiskCapacityGB 100|Select-Object -ExpandProperty Keys)|Should -Contain '\\?\Volume{00000000-0000-0000-0000-000000000002}\' + (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector -MinDiskCapacityGB 100).Keys | Should -Contain '\\?\Volume{00000000-0000-0000-0000-000000000002}\' } } Context 'When MinDiskCapacity Parameter is Not Defined for Fixed Disks' { It 'Should Include E: In The List of Eligible Fixed Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Contain 'E:' + (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Contain 'E:' } It 'Should Include Volume \\?\Volume{00000000-0000-0000-0000-000000000002}\ In The List of Eligible Removable Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Contain '\\?\Volume{00000000-0000-0000-0000-000000000002}\' + (Get-AutoBitlockerStatus -DriveType 'Fixed' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Contain '\\?\Volume{00000000-0000-0000-0000-000000000002}\' } } Context 'When MinDiskCapacity Parameter is Defined at 100 GB for Removable Disks' { It 'Should Exclude \\?\Volume{00000000-0000-0000-0000-000000000003}\ from The List of Eligible Fixed Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector -MinDiskCapacityGB 100|Select-Object -ExpandProperty Keys)|Should -Not -Contain '\\?\Volume{00000000-0000-0000-0000-000000000003}\' + (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector -MinDiskCapacityGB 100).Keys | Should -Not -Contain '\\?\Volume{00000000-0000-0000-0000-000000000003}\' } It 'Should Include F: In The List of Eligible Removable Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector -MinDiskCapacityGB 100|Select-Object -ExpandProperty Keys)|Should -Contain 'F:' + (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector -MinDiskCapacityGB 100).Keys | Should -Contain 'F:' } } Context 'When MinDiskCapacity Parameter is Not Defined for Fixed Disks' { It 'Should Include \\?\Volume{00000000-0000-0000-0000-000000000003}\ In The List of Eligible Fixed Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Contain '\\?\Volume{00000000-0000-0000-0000-000000000003}\' + (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Contain '\\?\Volume{00000000-0000-0000-0000-000000000003}\' } It 'Should Include F: In The List of Eligible Removable Volumes' { - (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector|Select-Object -ExpandProperty Keys)|Should -Contain 'F:' + (Get-AutoBitlockerStatus -DriveType 'Removable' -PrimaryProtector RecoveryPasswordProtector).Keys | Should -Contain 'F:' } } } diff --git a/Tests/Unit/MSFT_xBLBitlocker.tests.ps1 b/tests/Unit/MSFT_xBLBitlocker.tests.ps1 similarity index 69% rename from Tests/Unit/MSFT_xBLBitlocker.tests.ps1 rename to tests/Unit/MSFT_xBLBitlocker.tests.ps1 index f1d26cb..568dffe 100644 --- a/Tests/Unit/MSFT_xBLBitlocker.tests.ps1 +++ b/tests/Unit/MSFT_xBLBitlocker.tests.ps1 @@ -1,41 +1,35 @@ #region HEADER -$script:DSCModuleName = 'xBitlocker' -$script:DSCResourceName = 'MSFT_xBLBitlocker' - -# Unit Test Template Version: 1.2.4 -$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) -if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) -{ - & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) -} - -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force - -$TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -ResourceType 'Mof' ` - -TestType Unit - -#endregion HEADER +$script:dscModuleName = 'xBitlocker' +$script:dscResourceName = 'MSFT_xBLBitlocker' function Invoke-TestSetup { + try + { + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' + } + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Unit' } function Invoke-TestCleanup { - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Restore-TestEnvironment -TestEnvironment $script:testEnvironment } -# Begin Testing +Invoke-TestSetup + try { - Invoke-TestSetup - - InModuleScope $script:DSCResourceName { + InModuleScope $script:dscResourceName { # Override helper functions function Assert-HasPrereqsForBitlocker {} diff --git a/Tests/Unit/MSFT_xBLTpm.tests.ps1 b/tests/Unit/MSFT_xBLTpm.tests.ps1 similarity index 79% rename from Tests/Unit/MSFT_xBLTpm.tests.ps1 rename to tests/Unit/MSFT_xBLTpm.tests.ps1 index 03de837..5212587 100644 --- a/Tests/Unit/MSFT_xBLTpm.tests.ps1 +++ b/tests/Unit/MSFT_xBLTpm.tests.ps1 @@ -1,41 +1,35 @@ #region HEADER -$script:DSCModuleName = 'xBitlocker' -$script:DSCResourceName = 'MSFT_xBLTpm' - -# Unit Test Template Version: 1.2.4 -$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) -if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) -{ - & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) -} - -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force - -$TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -ResourceType 'Mof' ` - -TestType Unit - -#endregion HEADER +$script:dscModuleName = 'xBitlocker' +$script:dscResourceName = 'MSFT_xBLTpm' function Invoke-TestSetup { + try + { + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' + } + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Unit' } function Invoke-TestCleanup { - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Restore-TestEnvironment -TestEnvironment $script:testEnvironment } -# Begin Testing +Invoke-TestSetup + try { - Invoke-TestSetup - - InModuleScope $script:DSCResourceName { + InModuleScope $script:dscResourceName { # Override helper functions function Assert-HasPrereqsForBitlocker {} diff --git a/tests/Unit/xBitlocker.Common.tests.ps1 b/tests/Unit/xBitlocker.Common.tests.ps1 new file mode 100644 index 0000000..d21629c --- /dev/null +++ b/tests/Unit/xBitlocker.Common.tests.ps1 @@ -0,0 +1,877 @@ +#region HEADER +$script:projectPath = "$PSScriptRoot\..\.." | Convert-Path +$script:projectName = (Get-ChildItem -Path "$script:projectPath\*\*.psd1" | Where-Object -FilterScript { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try { Test-ModuleManifest -Path $_.FullName -ErrorAction Stop } catch { $false }) + }).BaseName + +$script:parentModule = Get-Module -Name $script:projectName -ListAvailable | Select-Object -First 1 +$script:subModulesFolder = Join-Path -Path $script:parentModule.ModuleBase -ChildPath 'Modules' +Remove-Module -Name $script:parentModule -Force -ErrorAction 'SilentlyContinue' + +$script:subModuleName = (Split-Path -Path $PSCommandPath -Leaf) -replace '\.Tests.ps1' +$script:subModuleFile = Join-Path -Path $script:subModulesFolder -ChildPath "$($script:subModuleName)" + +Import-Module $script:subModuleFile -Force -ErrorAction 'Stop' +#endregion HEADER + +InModuleScope $script:subModuleName { + function Get-BitlockerVolume + { + param + ( + [Parameter()] + [System.String] + $MountPoint + ) + } + + Describe 'xBitlocker.Common\Test-BitlockerEnabled' { + + Context 'When OS Volume is not Encrypted and No Key Protectors Assigned' { + Mock ` + -CommandName Get-BitlockerVolume ` + -MockWith { + # Decrypted with no Key Protectors + return @{ + VolumeType = 'OperatingSystem' + MountPoint = $MountPoint + CapacityGB = 500 + VolumeStatus = 'FullyDecrypted' + EncryptionPercentage = 0 + KeyProtector = @() + AutoUnlockEnabled = $null + ProtectionStatus = 'Off' + } + } + + It 'Should Fail The Test (TPM and RecoveryPassword Protectors)' { + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TPMProtector' -RecoveryPasswordProtector $true | Should -Be $false + } + } + + Context 'When OS Volume is Encrypted using TPM and Recovery Password Protectors' { + Mock ` + -CommandName Get-BitlockerVolume ` + -MockWith { + # Encrypted with TPM and Recovery Password Key Protectors + return @{ + VolumeType = 'OperatingSystem' + MountPoint = $MountPoint + CapacityGB = 500 + VolumeStatus = 'FullyEncrypted' + EncryptionPercentage = 100 + KeyProtector = @( + @{ + KeyProtectorType = 'Tpm' + }, + @{ + KeyProtectorType = 'RecoveryPassword' + } + ) + AutoUnlockEnabled = $null + ProtectionStatus = 'On' + } + } + + It 'Should Pass The Test (TPM and RecoveryPassword Protectors)' { + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TPMProtector' -RecoveryPasswordProtector $true -verbose | Should -Be $true + } + } + + Context 'When OS Volume is Decrypted, but has TPM and Recovery Password Protectors assigned' { + Mock ` + -CommandName Get-BitlockerVolume ` + -MockWith { + # Encrypted with TPM and Recovery Password Key Protectors + return @{ + VolumeType = 'OperatingSystem' + MountPoint = $MountPoint + CapacityGB = 500 + VolumeStatus = 'FullyDecrypted' + EncryptionPercentage = 0 + KeyProtector = @( + @{ + KeyProtectorType = 'Tpm' + }, + @{ + KeyProtectorType = 'RecoveryPassword' + } + ) + AutoUnlockEnabled = $null + ProtectionStatus = 'Off' + } + } + + It 'Should Fail The Test (TPM and RecoveryPassword Protectors)' { + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TPMProtector' -RecoveryPasswordProtector $true | Should -Be $false + } + } + + Context 'When Test-BitlockerEnabled is called and Get-BitlockerVolume returns null' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable + + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' | Should -Be $false + } + } + + Context 'When Test-BitlockerEnabled is called and Get-BitlockerVolume returns a volume with no key protectors' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { + return @{ + KeyProtector = $null + } + } + + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' | Should -Be $false + } + } + + Context 'When Test-BitlockerEnabled is called, AutoUnlock is requested on a non-OS disk, and AutoUnlock is not enabled' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { + return @{ + AutoUnlockEnabled = $false + VolumeType = 'Data' + KeyProtector = @('Protector1') + } + } + + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -AutoUnlock $true | Should -Be $false + } + } + + $defaultBLV = @( + @{ + KeyProtector = @('Protector1') + } + ) + + $fakePin = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'fakepin', (New-Object -TypeName System.Security.SecureString) + + Context 'When Test-BitlockerEnabled is called, a AdAccountOrGroupProtector protector is requested, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } + + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -AdAccountOrGroupProtector $true | Should -Be $false + } + } + + Context 'When Test-BitlockerEnabled is called, a PasswordProtector protector is requested, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } + + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -PasswordProtector $true | Should -Be $false + } + } + + Context 'When Test-BitlockerEnabled is called, a Pin protector is requested, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } + + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -Pin $fakePin | Should -Be $false + } + } + + Context 'When Test-BitlockerEnabled is called, a RecoveryKeyProtector protector is requested, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } + + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -RecoveryKeyProtector $true | Should -Be $false + } + } + + Context 'When Test-BitlockerEnabled is called, a RecoveryPasswordProtector protector is requested, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } + + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -RecoveryPasswordProtector $true | Should -Be $false + } + } + + Context 'When Test-BitlockerEnabled is called, a StartupKeyProtector protector is requested without a primary TPM protector, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } + + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'StartupKeyProtector' -StartupKeyProtector $true | Should -Be $false + } + } + + Context 'When Test-BitlockerEnabled is called, a StartupKeyProtector protector is requested with a primary TPM protector, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } + + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -StartupKeyProtector $true | Should -Be $false + } + } + + Context 'When Test-BitlockerEnabled is called, a TpmProtector protector is requested, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false } + + Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -TpmProtector $true | Should -Be $false + } + } + } + + Describe 'xBitlockerCommon\Assert-HasPrereqsForBitlocker' { + function Get-WindowsFeature + { + param + ( + [string] + $FeatureName + ) + } + + function Get-OSEdition + { + + } + + Context 'When OS is Server Core and all required features are installed' { + Mock -CommandName Get-OSEdition -MockWith { + 'Server Core' + } + + Mock -CommandName Get-WindowsFeature -MockWith { + if ($FeatureName -eq 'RSAT-Feature-Tools-BitLocker-RemoteAdminTool') + { + return $null + } + else + { + return @{ + DisplayName = $FeatureName + Name = $FeatureName + InstallState = 'Installed' + } + } + } + + It 'Should not generate any error messages' { + Mock -CommandName Write-Error + Assert-HasPrereqsForBitlocker + Assert-MockCalled -Command Write-Error -Exactly -Times 0 -Scope It + } + + It 'Should run the Assert-HasPrereqsForBitlocker function without exceptions' { + {Assert-HasPrereqsForBitlocker} | Should -Not -Throw + } + } + + Context 'When OS is Full Server and all required features are installed' { + Mock -CommandName Get-OSEdition -MockWith { + return 'Server' + } + + Mock -CommandName Get-WindowsFeature -MockWith { + param + ( + [string] + $FeatureName + ) + + return @{ + DisplayName = $FeatureName + Name = $FeatureName + InstallState = 'Installed' + } + } + + It 'Should not generate any error messages' { + Mock -CommandName Write-Error + Assert-HasPrereqsForBitlocker + Assert-MockCalled -Command Write-Error -Exactly -Times 0 -Scope It + } + + It 'Should run the Assert-HasPrereqsForBitlocker function without exceptions' { + {Assert-HasPrereqsForBitlocker} | Should -Not -Throw + } + } + + Context 'When OS is Full Server without the required features installed' { + Mock -CommandName Get-OSEdition -MockWith { + return 'Server' + } + + Mock -CommandName Get-WindowsFeature -MockWith { + return @{ + DisplayName = $FeatureName + Name = $FeatureName + InstallState = 'Available' + } + } + + Mock -CommandName Write-Error + + It 'Should give an error that Bitlocker Windows Feature needs to be installed' { + {Assert-HasPrereqsForBitlocker} | Should -Throw + Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter { + $Message -eq 'The Bitlocker feature needs to be installed before the xBitlocker module can be used' + } + } + + It 'Should give an error that RSAT-Feature-Tools-BitLocker Windows Feature needs to be installed' { + {Assert-HasPrereqsForBitlocker} | Should -Throw + Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter { + $Message -eq 'The RSAT-Feature-Tools-BitLocker feature needs to be installed before the xBitlocker module can be used' + } + } + + It 'Should give an error that RSAT-Feature-Tools-BitLocker-RemoteAdminTool Windows Feature needs to be installed' { + {Assert-HasPrereqsForBitlocker} | Should -Throw + Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter { + $Message -eq 'The RSAT-Feature-Tools-BitLocker-RemoteAdminTool feature needs to be installed before the xBitlocker module can be used' + } + } + + It 'The Assert-HasPrereqsForBitlocker function should throw an exceptions about missing required Windows Features' { + {Assert-HasPrereqsForBitlocker} | Should -Throw 'Required Bitlocker features need to be installed before xBitlocker can be used' + } + } + + Context 'When OS is Server Core without the required features installed' { + Mock -CommandName Get-OSEdition -MockWith { + return 'Server Core' + } + + Mock -CommandName Get-WindowsFeature -MockWith { + param + ( + [string] + $FeatureName + ) + + if ($FeatureName -eq 'RSAT-Feature-Tools-BitLocker-RemoteAdminTool') + { + return $null + } + else + { + + return @{ + DisplayName = $FeatureName + Name = $FeatureName + InstallState = 'Available' + } + } + } + + Mock -CommandName Write-Error + + It 'Should give an error that Bitlocker Windows Feature needs to be installed' { + {Assert-HasPrereqsForBitlocker} | Should -Throw + Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter { + $Message -eq 'The Bitlocker feature needs to be installed before the xBitlocker module can be used' + } + } + + It 'Should give an error that RSAT-Feature-Tools-BitLocker Windows Feature needs to be installed' { + {Assert-HasPrereqsForBitlocker} | Should -Throw + Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter { + $Message -eq 'The RSAT-Feature-Tools-BitLocker feature needs to be installed before the xBitlocker module can be used' + } + } + + It 'Should not give an error that RSAT-Feature-Tools-BitLocker-RemoteAdminTool Windows Feature needs to be installed as this Windows Features is not available on Server Core.' { + {Assert-HasPrereqsForBitlocker} | Should -Throw + Assert-MockCalled -Command Write-Error -Exactly -Times 0 -Scope It -ParameterFilter { + $Message -eq 'The RSAT-Feature-Tools-BitLocker-RemoteAdminTool feature needs to be installed before the xBitlocker module can be used' + } + } + + It 'The Assert-HasPrereqsForBitlocker function should throw an exceptions about missing required Windows Features' { + {Assert-HasPrereqsForBitlocker} | Should -Throw 'Required Bitlocker features need to be installed before xBitlocker can be used' + } + } + } + + Describe 'xBitLockerCommon\Get-OSEdition' { + It 'Should return "Server Core" if the OS is Windows Server Core' { + Mock -CommandName Get-ItemProperty -MockWith { + [PSCustomObject] @{ + InstallationType = 'Server Core' + PSPath = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt\currentversion' + PSParentPath = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt' + PSChildName = 'currentversion' + PSDrive = 'HKLM' + PSProvider = 'Microsoft.PowerShell.Core\Registry' + } + } + + $OSVersion = Get-OSEdition + $OSVersion | Should -Be 'Server Core' + } + + It 'Should return "Server" if the OS is Full Windows Server' { + Mock -CommandName Get-ItemProperty -MockWith { + [PSCustomObject] @{ + InstallationType = 'Server' + PSPath = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt\currentversion' + PSParentPath = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt' + PSChildName = 'currentversion' + PSDrive = 'HKLM' + PSProvider = 'Microsoft.PowerShell.Core\Registry' + } + } + + $OSVersion = Get-OSEdition + $OSVersion | Should -Be 'Server' + } + + It 'Should run without exceptions' { + Mock -CommandName Get-ItemProperty -MockWith { + [PSCustomObject] @{ + InstallationType = 'Some other os' + PSPath = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt\currentversion' + PSParentPath = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt' + PSChildName = 'currentversion' + PSDrive = 'HKLM' + PSProvider = 'Microsoft.PowerShell.Core\Registry' + } + } + {Get-OSEdition} | Should -Not -Throw + } + } + + Describe 'xBitLockerCommon\Enable-BitlockerInternal' -Tag 'Helper' { + # Override Bitlocker cmdlets + function Enable-Bitlocker {} + function Enable-BitlockerAutoUnlock {} + + AfterEach { + Assert-VerifiableMock + } + + $fakePin = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'fakepin', (New-Object -TypeName System.Security.SecureString) + $mountPoint = 'C:' + $encryptedBLV = @{ + VolumeStatus = 'FullyEncrypted' + } + $encryptedOSBLV = @{ + VolumeStatus = 'FullyEncrypted' + VolumeType = 'OperatingSystem' + } + $decryptedOSBLV = @{ + VolumeStatus = 'FullyDecrypted' + VolumeType = 'OperatingSystem' + } + + Context 'When Enable-BitlockerInternal is called Get-BitlockerVolume returns null' { + It 'Should throw an exception' { + Mock -CommandName Get-BitLockerVolume -Verifiable + + { Enable-BitlockerInternal -MountPoint 'C:' -Pin $fakePin -PrimaryProtector 'PasswordProtector' } | Should -Throw -ExpectedMessage 'Unable to find Bitlocker Volume associated with Mount Point' + } + } + + Context 'When Enable-BitlockerInternal is called with TpmProtector set to True and PrimaryProtector not set to TpmProtector' { + $badPrimaryProtectorCases = @( + @{ + PrimaryProtector = 'PasswordProtector' + } + + @{ + PrimaryProtector = 'RecoveryPasswordProtector' + } + + @{ + PrimaryProtector = 'StartupKeyProtector' + } + ) + + It 'Should throw an exception' -TestCases $badPrimaryProtectorCases { + param + ( + [System.String] + $PrimaryProtector + ) + + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + + { Enable-BitlockerInternal -MountPoint $mountPoint -TpmProtector $true -PrimaryProtector $PrimaryProtector } | Should -Throw -ExpectedMessage 'If TpmProtector is used, it must be the PrimaryProtector.' + } + } + + Context 'When Enable-BitlockerInternal is called with Pin specified and TpmProtector not specified' { + It 'Should throw an exception' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + + { Enable-BitlockerInternal -MountPoint $mountPoint -Pin $fakePin -PrimaryProtector 'PasswordProtector' } | Should -Throw -ExpectedMessage 'A TpmProtector must be used if Pin is used.' + } + } + + Context 'When Enable-BitlockerInternal is called with Pin specified and TpmProtector not specified' { + It 'Should throw an exception' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + + { Enable-BitlockerInternal -MountPoint $mountPoint -Pin $fakePin -PrimaryProtector 'PasswordProtector' } | Should -Throw -ExpectedMessage 'A TpmProtector must be used if Pin is used.' + } + } + + $defaultEnableParams = @{ + MountPoint = $mountPoint + Pin = $fakePin + PrimaryProtector = 'TpmProtector' + TpmProtector = $true + EncryptionMethod = 'Aes256' + HardwareEncryption = $true + Service = $true + SkipHardwareTest = $true + UsedSpaceOnly = $true + AllowImmediateReboot = $true + StartupKeyProtector = $true + } + + Context 'When Enable-BitlockerInternal is called and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedOSBLV } + Mock -CommandName Start-Sleep -Verifiable + Mock -CommandName Restart-Computer -Verifiable + + Enable-BitlockerInternal @defaultEnableParams + } + } + + Context 'When Enable-BitlockerInternal is called, the volume is not yet encrypted, and Enable-Bitlocker does not return a result' { + It 'Should throw an exception' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -Verifiable + + { Enable-BitlockerInternal @defaultEnableParams } | Should -Throw -ExpectedMessage 'Failed to successfully enable Bitlocker on MountPoint' + } + } + + Context 'When Enable-BitlockerInternal is called, the volume is not yet encrypted and is not an OS drive, and AutoUnlock is specified' { + It 'Should enable Bitlocker with the correct key protectors and parameters and enable AutoUnlock' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { + return @{ + VolumeStatus = 'FullyDecrypted' + VolumeType = 'Data' + } + } + Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedBLV } + Mock -CommandName Enable-BitlockerAutoUnlock -Verifiable + + $defaultEnableParams.Add('AutoUnlock', $true) + + Enable-BitlockerInternal @defaultEnableParams + + $defaultEnableParams.Remove('AutoUnlock') + } + } + + Context 'When Enable-BitlockerInternal is called with TPM only and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedBLV } + + $tpmOnlyEnableParams = @{ + MountPoint = $mountPoint + PrimaryProtector = 'TpmProtector' + TpmProtector = $true + } + + Enable-BitlockerInternal @tpmOnlyEnableParams + } + } + + Context 'When Enable-BitlockerInternal is called with TPM and pin only and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedBLV } + + $tpmAndPinOnlyEnableParams = @{ + MountPoint = $mountPoint + PrimaryProtector = 'TpmProtector' + TpmProtector = $true + Pin = $fakePin + } + + Enable-BitlockerInternal @tpmAndPinOnlyEnableParams + } + } + + Context 'When Enable-BitlockerInternal is called with TPM and pin only and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedBLV } + + $tpmAndStartupOnlyEnableParams = @{ + MountPoint = $mountPoint + PrimaryProtector = 'TpmProtector' + TpmProtector = $true + StartupKeyProtector = $true + StartupKeyPath = 'C:\' + } + + Enable-BitlockerInternal @tpmAndStartupOnlyEnableParams + } + } + + Context 'When Enable-BitlockerInternal is called with a Password Protector and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -MockWith { return $encryptedBLV } + + $passwordEnableParams = @{ + MountPoint = $mountPoint + PrimaryProtector = 'PasswordProtector' + PasswordProtector = $true + Password = $fakePin + } + + Enable-BitlockerInternal @passwordEnableParams + } + } + + Context 'When Enable-BitlockerInternal is called with a Recovery Password Protector and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -MockWith { return $encryptedBLV } + + $recoveryPasswordEnableParams = @{ + MountPoint = $mountPoint + PrimaryProtector = 'RecoveryPasswordProtector' + RecoveryPasswordProtector = $true + Password = $fakePin + } + + Enable-BitlockerInternal @recoveryPasswordEnableParams + } + } + + Context 'When Enable-BitlockerInternal is called with a StartupKey Protector and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -MockWith { return $encryptedBLV } + + $startupKeyEnableParams = @{ + MountPoint = $mountPoint + PrimaryProtector = 'StartupKeyProtector' + StartupKeyProtector = $true + StartupKeyPath = 'C:\Path' + } + + Enable-BitlockerInternal @startupKeyEnableParams + } + } + } + + Describe 'xBitLockerCommon\Add-MissingBitLockerKeyProtector' -Tag 'Helper' { + # Override Bitlocker cmdlets + function Add-BitLockerKeyProtector {} + + # Suppress Write-Verbose output + Mock -CommandName Write-Verbose + + AfterEach { + Assert-VerifiableMock + } + + $fakePin = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'fakepin', (New-Object -TypeName System.Security.SecureString) + $mountPoint = 'C:' + $encryptedBLV = @{ + VolumeStatus = 'FullyEncrypted' + } + + Context 'When Add-MissingBitLockerKeyProtector is called, the AdAccountOrGroupProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { + It 'Should add the AdAccountOrGroupProtector protector' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false} + Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'AdAccountOrGroupProtector'} + + Enable-BitlockerInternal -MountPoint 'AdAccountOrGroupProtector' -Pin $fakePin -PrimaryProtector 'TpmProtector' -TpmProtector $true -AdAccountOrGroupProtector $true + } + } + + Context 'When Add-MissingBitLockerKeyProtector is called, the PasswordProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { + It 'Should add the PasswordProtector protector' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false} + Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'PasswordProtector'} + + Enable-BitlockerInternal -MountPoint 'PasswordProtector' -Pin $fakePin -PrimaryProtector 'TpmProtector' -TpmProtector $true -PasswordProtector $true + } + } + + Context 'When Add-MissingBitLockerKeyProtector is called, the RecoveryKeyProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { + It 'Should add the RecoveryKeyProtector protector' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false} + Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'RecoveryKeyProtector'} + + Enable-BitlockerInternal -MountPoint 'RecoveryKeyProtector' -Pin $fakePin -PrimaryProtector 'TpmProtector' -TpmProtector $true -RecoveryKeyProtector $true + } + } + + Context 'When Add-MissingBitLockerKeyProtector is called, the RecoveryPasswordProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { + It 'Should add the RecoveryPasswordProtector protector' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false} + Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'RecoveryPasswordProtector'} + + Enable-BitlockerInternal -MountPoint 'RecoveryPasswordProtector' -Pin $fakePin -PrimaryProtector 'TpmProtector' -TpmProtector $true -RecoveryPasswordProtector $true + } + } + + Context 'When Add-MissingBitLockerKeyProtector is called, the StartupKeyProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { + It 'Should add the StartupKeyProtector protector' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + Mock -CommandName Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false} + Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'StartupKeyProtector'} + + Enable-BitlockerInternal -MountPoint 'StartupKeyProtector' -PrimaryProtector 'RecoveryPasswordProtector' -RecoveryPasswordProtector $true -StartupKeyProtector $true + } + } + } + + Describe 'xBitLockerCommon\Test-CollectionContainsKeyProtector' -Tag 'Helper' { + $testKeyProtectorCollection = @( + @{ + KeyProtectorType = 'RecoveryPassword' + } + + @{ + KeyProtectorType = 'AdAccountOrGroup' + } + + @{ + KeyProtectorType = 'StartupKeyProtector' + } + ) + + Context 'When Test-CollectionContainsKeyProtector is called and the target KeyProtector exists in the collection' { + It 'Should return True' { + Test-CollectionContainsKeyProtector -Type 'AdAccountOrGroup' -KeyProtectorCollection $testKeyProtectorCollection | Should -Be $true + } + } + + Context 'When Test-CollectionContainsKeyProtector is called and the target KeyProtector does not exist in the collection' { + It 'Should return False' { + Test-CollectionContainsKeyProtector -Type 'AdAccountOrGroup2' -KeyProtectorCollection $testKeyProtectorCollection | Should -Be $false + } + } + + Context 'When Test-CollectionContainsKeyProtector is called with the StartsWith switch and the target KeyProtector exists in the collection' { + It 'Should return True' { + Test-CollectionContainsKeyProtector -Type 'AdAccount' -KeyProtectorCollection $testKeyProtectorCollection -StartsWith $true | Should -Be $true + } + } + + Context 'When Test-CollectionContainsKeyProtector is called with the StartsWith switch and the target KeyProtector does not exist in the collection' { + It 'Should return False' { + Test-CollectionContainsKeyProtector -Type 'Account' -KeyProtectorCollection $testKeyProtectorCollection -StartsWith $true | Should -Be $false + } + } + + Context 'When Test-CollectionContainsKeyProtector is called with the Contains switch and the target KeyProtector exists in the collection' { + It 'Should return True' { + Test-CollectionContainsKeyProtector -Type 'Account' -KeyProtectorCollection $testKeyProtectorCollection -Contains $true | Should -Be $true + } + } + + Context 'When Test-CollectionContainsKeyProtector is called with the Contains switch and the target KeyProtector does not exist in the collection' { + It 'Should return False' { + Test-CollectionContainsKeyProtector -Type 'NotInCollection' -KeyProtectorCollection $testKeyProtectorCollection -Contains $true | Should -Be $false + } + } + } + + Describe 'xBitLockerCommon\Add-ToPSBoundParametersFromHashtable' -Tag 'Helper' { + AfterEach { + Assert-VerifiableMock + } + + Context 'When Add-ToPSBoundParametersFromHashtable is called, a parameter is added, and a parameter is changed' { + It 'Should add a new parameter and change the existing parameter' { + $param1 = 'abc' + $param2 = $null + $param2new = 'notnull' + $param3 = 'def' + $param4 = 'ghi' + + $psBoundParametersIn = @{ + Param1 = $param1 + Param2 = $param2 + Param3 = $param3 + } + + $paramsToAdd = @{ + Param2 = $param2new + Param4 = $param4 + } + + Add-ToPSBoundParametersFromHashtable -PSBoundParametersIn $psBoundParametersIn -ParamsToAdd $paramsToAdd + + $psBoundParametersIn.ContainsKey('Param1') -and $psBoundParametersIn['Param1'] -eq $param1 | Should -Be $true + $psBoundParametersIn.ContainsKey('Param2') -and $psBoundParametersIn['Param2'] -eq $param2new | Should -Be $true + $psBoundParametersIn.ContainsKey('Param3') -and $psBoundParametersIn['Param3'] -eq $param3 | Should -Be $true + $psBoundParametersIn.ContainsKey('Param4') -and $psBoundParametersIn['Param4'] -eq $param4 | Should -Be $true + } + } + } + + Describe 'xBitLockerCommon\Remove-FromPSBoundParametersUsingHashtable' -Tag 'Helper' { + AfterEach { + Assert-VerifiableMock + } + + Context 'When Remove-FromPSBoundParametersUsingHashtable is called and both ParamsToKeep and ParamsToRemove are specified' { + It 'Should throw an exception' { + { Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn @{} -ParamsToKeep @('Param1') -ParamsToRemove @('Param2') } | ` + Should -Throw -ExpectedMessage 'Parameter set cannot be resolved using the specified named parameters.' + } + } + + Context 'When Remove-FromPSBoundParametersUsingHashtable is called with ParamsToKeep' { + It 'Should remove any parameter not specified in ParamsToKeep' { + $psBoundParametersIn = @{ + Param1 = 1 + Param2 = 2 + Param3 = 3 + } + + $paramsToKeep = @('Param1', 'Param2') + + Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn $psBoundParametersIn -ParamsToKeep $paramsToKeep + + $psBoundParametersIn.ContainsKey('Param1') | Should -Be $true + $psBoundParametersIn.ContainsKey('Param2') | Should -Be $true + $psBoundParametersIn.ContainsKey('Param3') | Should -Be $false + } + } + + Context 'When Remove-FromPSBoundParametersUsingHashtable is called with ParamsToRemove' { + It 'Should remove any parameter specified in ParamsToRemove' { + $psBoundParametersIn = @{ + Param1 = 1 + Param2 = 2 + Param3 = 3 + } + + $paramsToRemove = @( + 'Param1', + 'param2' + ) + + Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn $psBoundParametersIn -ParamsToRemove $paramsToRemove + + $psBoundParametersIn.ContainsKey('Param1') | Should -Be $false + $psBoundParametersIn.ContainsKey('Param2') | Should -Be $false + $psBoundParametersIn.ContainsKey('Param3') | Should -Be $true + } + } + } +} diff --git a/xBitlocker.psd1 b/xBitlocker.psd1 deleted file mode 100644 index 99ac541..0000000 --- a/xBitlocker.psd1 +++ /dev/null @@ -1,121 +0,0 @@ - - -@{ - -# Script module or binary module file associated with this manifest. -# RootModule = '' - -# Version number of this module. -moduleVersion = '1.4.0.0' - -# ID used to uniquely identify this module -GUID = 'dc4f3fd0-4e1d-4916-84f8-d0bb89d52507' - -# Author of this module -Author = 'Microsoft Corporation' - -# Company or vendor of this module -CompanyName = 'Microsoft Corporation' - -# Copyright statement for this module -Copyright = '(c) 2018 Microsoft Corporation. All rights reserved.' - -# Description of the functionality provided by this module -Description = 'This DSC Module allows you to configure Bitlocker on a single disk, configure a TPM chip, or automatically enable Bitlocker on multiple disks.' - -# Minimum version of the Windows PowerShell engine required by this module -PowerShellVersion = '4.0' - -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of Microsoft .NET Framework required by this module -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module -CLRVersion = '4.0' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() - -# Functions to export from this module -FunctionsToExport = '*' - -# Cmdlets to export from this module -CmdletsToExport = '*' - -# Variables to export from this module -VariablesToExport = '*' - -# Aliases to export from this module -AliasesToExport = '*' - -# List of all modules packaged with this module -# ModuleList = @() - -# List of all files packaged with this module -# FileList = @() - -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ - - PSData = @{ - - # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('DesiredStateConfiguration', 'DSC', 'DSCResourceKit', 'DSCResource') - - # A URL to the license for this module. - LicenseUri = 'https://github.com/PowerShell/xBitlocker/blob/master/LICENSE' - - # A URL to the main website for this project. - ProjectUri = 'https://github.com/PowerShell/xBitlocker' - - # A URL to an icon representing this module. - # IconUri = '' - - # ReleaseNotes of this module - ReleaseNotes = '- Change double quoted string literals to single quotes -- Add spaces between array members -- Add spaces between variable types and variable names -- Add spaces between comment hashtag and comments -- Explicitly removed extra hidden files from release package - -' - - } # End of PSData hashtable - -} # End of PrivateData hashtable - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' - -} - - - - -